Skip to content

Model Card for LayoutFormer++

arXiv venue license base dataset dataset vendor-parity hub

This package ports LayoutFormer++, the autoregressive layout transformer method, into a 🤗transformers-style pipeline.

Model Details

Model Description

LayoutFormer++ is a transformers layout generator that models layout sequences across label, label-size, relation, refinement, completion, and unconditional tasks. Converted checkpoints use task-specific token vocabularies for RICO25 and PubLayNet while preserving the original-implementation discrete layout representation internally. Public outputs use normalized center xywh boxes in [0, 1], dataset-local integer labels, a valid-element mask, and id2label.

Public outputs omit generated elements whose labels cannot be mapped to the selected dataset, and one warning reports the number omitted. The original parser keeps these elements with error label 0 and its parsed box, which may be shifted by truncation; this package deliberately drops them because the public schema has no unknown-label category, so element-count and box metrics may differ from vendor results.

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

Model Sources

Supported Checkpoints

Checkpoint Hub ID Status
RICO25 label creative-graphic-design/layoutformerpp-rico25-label not-published
RICO25 label-size creative-graphic-design/layoutformerpp-rico25-label-size not-published
RICO25 relation creative-graphic-design/layoutformerpp-rico25-relation not-published
RICO25 refinement creative-graphic-design/layoutformerpp-rico25-refinement not-published
RICO25 completion creative-graphic-design/layoutformerpp-rico25-completion not-published
RICO25 unconditional creative-graphic-design/layoutformerpp-rico25-unconditional not-published
PubLayNet label creative-graphic-design/layoutformerpp-publaynet-label not-published
PubLayNet label-size creative-graphic-design/layoutformerpp-publaynet-label-size not-published
PubLayNet relation creative-graphic-design/layoutformerpp-publaynet-relation not-published
PubLayNet refinement creative-graphic-design/layoutformerpp-publaynet-refinement not-published
PubLayNet completion creative-graphic-design/layoutformerpp-publaynet-completion not-published
PubLayNet unconditional creative-graphic-design/layoutformerpp-publaynet-unconditional not-published

Uses

Direct Use

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

Each released task has an incompatible task-specific checkpoint, so Hub ids include the task suffix. Supported public conditions map to tasks in the original implementation as follows: gen_t -> label, gen_ts -> label_size, gen_r -> relation, refinement -> refinement, completion -> completion, and ugen -> unconditional.

Task inputs follow the public condition shape for the selected checkpoint: label takes labels=[["Text", "Image"]]; label_size adds matching normalized center xywh boxes; relation takes five-integer relation tuples such as (0, 1, 2, 3, 1) for (subject_index, relation_id, object_index, constraint_id, value); completion and refinement take partial labels, bbox, and mask; unconditional needs no layout condition. If normalized=False, pass canvas_size=(width, height) so pixel boxes can be converted to the internal discrete ltwh grid.

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 shared packages when they are not published on PyPI.

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

Clone this repository, install the workspace member, and run the download and conversion steps in REPRODUCING.md. Those steps create .cache/layoutformerpp/converted/rico_gen_t.

git clone https://github.com/creative-graphic-design/design-generators.git
cd design-generators
uv sync --package layoutformerpp
uv run --package layoutformerpp python
from layoutformerpp import LayoutFormerPPPipeline

path = ".cache/layoutformerpp/converted/rico_gen_t"
# After Hub publication: from_pretrained("creative-graphic-design/layoutformerpp-rico25-label")
pipe = LayoutFormerPPPipeline.from_pretrained(path)
out = pipe(condition_type="label", labels=[["Text", "Image"]], max_length=16)

print(out.bbox)
print(out.labels)
print(out.id2label)

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

RICO25 public outputs use zero-based dataset-local ids, while the internal LayoutFormer++ tokenizer uses one-based label_<id> tokens. PubLayNet COCO-style document boxes are converted to the internal discrete LayoutFormer++ ltwh token grid and returned publicly as normalized center xywh.

Training Procedure

This package ports released behavior and exposes the package-local training path; current trained-checkpoint reproduction status is recorded in TRAINING.md.

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; staged S0-S4 reproduction passes for all twelve recipe families, and trained-checkpoint S5 reproduction is not claimed.

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

Checkpoint Public checkpoint Vocab source Tokenizer Logits max abs Logits max rel Generation
rico_gen_t present ckpts/rico_gen_t/vocab.json exact 0.0 0.0 exact original-implementation greedy loop; exact label-constrained loop
rico_gen_ts present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation greedy loop; exact label-size-constrained loop
rico_gen_r present ckpts/rico_gen_r/vocab.json exact 0.0 0.0 exact original-implementation top-k loop with relation input
rico_refinement present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation greedy loop
rico_completion present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation top-k loop
rico_ugen present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation top-k loop
publaynet_gen_t present ckpts/publaynet_gen_t/vocab.json exact 0.0 0.0 exact original-implementation greedy loop; exact label-constrained loop
publaynet_gen_ts present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation greedy loop; exact label-size-constrained loop
publaynet_gen_r present ckpts/publaynet_gen_r/vocab.json exact 0.0 0.0 exact original-implementation top-k loop with relation input
publaynet_refinement present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation greedy loop
publaynet_completion present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation top-k loop
publaynet_ugen present synthetic task vocab; no task vocab.json is published exact 0.0 0.0 exact original-implementation top-k loop

The substantive agreement-check sweep covers 28 checkpoint/check combinations: 12 checkpoint-logit checks, 12 checkpoint-generation checks, and 4 label-constrained generation checks. The separate FileNotFoundError loader test is not counted as an agreement case.

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

Training, conversion, and agreement-check costs depend on the selected recipe, checkpoint, and local hardware.

Technical Specifications

Model Architecture and Objective

LayoutFormer++ uses an encoder-decoder Transformer with task-specific token vocabularies for label, label-size, relation, refinement, completion, and unconditional generation. Optional task prompt embeddings mirror the released checkpoints, while the processor decodes discrete tokens into normalized boxes and dataset labels.

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 against the original implementation.

Software

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

License

Repository wrapper code is Apache-2.0. The original implementation is MIT licensed in the parent Microsoft LayoutGeneration repository. Converted code in this package is for research conversion and evaluation of the released LayoutFormer++ checkpoints.

Citation

@inproceedings{jiang2023layoutformerpp,
  title = {LayoutFormer++: Conditional Graphic Layout Generation via Constraint Serialization and Decoding Space Restriction},
  author = {Jiang, Zhaoyun and Guo, Jiaqi and Sun, Shizhao and Deng, Huayu and Wu, Zhongkai and Mijovic, Vuksan and Yang, Zijiang James and Lou, Jian-Guang and Zhang, Dongmei},
  booktitle = {Proceedings of the IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR)},
  year = {2023}
}

API Reference

Transformers-style LayoutFormer++ components.

LayoutFormerPPConfig

Bases: PretrainedConfig

Stores model, tokenizer, and task defaults for converted checkpoints.

Source code in models/layoutformerpp/src/layoutformerpp/configuration_layoutformerpp.py
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
class LayoutFormerPPConfig(PretrainedConfig):
    """Stores model, tokenizer, and task defaults for converted checkpoints."""

    model_type = "layoutformerpp"

    def __init__(
        self,
        vocab_size: int = 0,
        max_position_embeddings: int | None = None,
        d_model: int = 512,
        encoder_layers: int = 8,
        decoder_layers: int | None = None,
        encoder_attention_heads: int = 8,
        decoder_attention_heads: int | None = None,
        dropout: float = 0.1,
        dim_feedforward: int | None = None,
        share_embedding: bool = True,
        dataset: DatasetName | str = DatasetName.rico25,
        task: LayoutFormerPPTask | ConditionType | str = LayoutFormerPPTask.gen_t,
        max_num_elements: int = 20,
        bbox_format: BoxFormat | str = BoxFormat.ltwh,
        default_box_format: BoxFormat | str = BoxFormat.xywh,
        discrete_x_grid: int = 128,
        discrete_y_grid: int = 128,
        add_sep_token: bool = True,
        sort_by_dict: bool = True,
        add_task_embedding: bool = False,
        add_task_prompt_token_in_model: bool = False,
        num_task_prompt_token: int = 1,
        task_id: int | None = None,
        decode_max_length: int | None = None,
        eval_seed: int | None = None,
        gen_t_add_unk_token: bool = False,
        gen_ts_add_unk_token: bool = False,
        gen_r_add_unk_token: bool = False,
        gen_r_compact: bool = False,
        bos_token_id: int = 0,
        eos_token_id: int = 1,
        pad_token_id: int = 2,
        is_encoder_decoder: bool = True,
        condition_type: ConditionType | str | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        architectures: list[str] | None = None,
        output_hidden_states: bool | None = False,
        output_attentions: bool | None = False,
        return_dict: bool | None = True,
        chunk_size_feed_forward: int = 0,
        problem_type: Literal[
            "regression", "single_label_classification", "multi_label_classification"
        ]
        | None = None,
        id2label: dict[int | str, str] | None = None,
        label2id: dict[str, int] | None = None,
        label_translation_metadata: LabelTranslationMetadata | None = None,
        torch_dtype: str | None = None,
        dtype: str | None = None,
        tie_word_embeddings: bool = True,
        task_specific_params: dict[
            str, str | int | float | bool | list[str | int | float | bool]
        ]
        | None = None,
        name_or_path: str = "",
        _name_or_path: str | None = None,
        _commit_hash: str | None = None,
        attn_implementation: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize architecture and task-specific generation defaults."""
        _ = (condition_type, model_type, transformers_version)
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        translation = label_translation_for_dataset(normalized_dataset)
        canonical_id2label = dict(translation.public_id2label)
        if (
            id2label is not None
            and {int(key): str(value) for key, value in id2label.items()}
            != canonical_id2label
        ):
            raise ValueError("id2label does not match the canonical dataset map")

        normalized_id2label: dict[int, str] = {
            key: value for key, value in canonical_id2label.items()
        }
        label2id = {value: key for key, value in canonical_id2label.items()}
        self.label_translation_metadata = (
            translation.metadata()
            if label_translation_metadata is None
            else validate_label_translation_metadata(
                label_translation_metadata,
                translation,
            )
        )
        condition_type = TASK_TO_CONDITION[normalized_task]
        self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
        self.task = str(normalized_task)
        self.condition_type = str(condition_type)
        defaults = TASK_DEFAULTS.get((normalized_dataset, condition_type), {})
        if max_position_embeddings is None:
            max_position_embeddings = int(defaults.get("max_position_embeddings", 150))
        if decode_max_length is None:
            decode_max_length = int(defaults.get("decode_max_length", 120))
        if eval_seed is None:
            eval_seed = int(defaults.get("eval_seed", 100))

        self.vocab_size = vocab_size
        self.max_position_embeddings = max_position_embeddings
        self.d_model = d_model
        self.encoder_layers = encoder_layers
        self.decoder_layers = (
            decoder_layers if decoder_layers is not None else encoder_layers
        )
        self.encoder_attention_heads = encoder_attention_heads
        self.decoder_attention_heads = (
            decoder_attention_heads
            if decoder_attention_heads is not None
            else encoder_attention_heads
        )
        self.dropout = dropout
        self.dim_feedforward = (
            dim_feedforward if dim_feedforward is not None else d_model * 4
        )
        self.share_embedding = share_embedding
        self.max_num_elements = max_num_elements
        self.bbox_format = str(normalize_box_format(bbox_format))
        self.default_box_format = str(normalize_box_format(default_box_format))

        self.discrete_x_grid = discrete_x_grid
        self.discrete_y_grid = discrete_y_grid
        self.add_sep_token = add_sep_token
        self.sort_by_dict = sort_by_dict
        self.add_task_embedding = add_task_embedding
        self.add_task_prompt_token_in_model = add_task_prompt_token_in_model
        self.num_task_prompt_token = num_task_prompt_token
        self.task_id = task_id

        self.decode_max_length = decode_max_length
        self.eval_seed = eval_seed
        self.gen_t_add_unk_token = gen_t_add_unk_token
        self.gen_ts_add_unk_token = gen_ts_add_unk_token
        self.gen_r_add_unk_token = gen_r_add_unk_token
        self.gen_r_compact = gen_r_compact

        super().__init__(
            transformers_version=transformers_version,
            architectures=architectures,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
            chunk_size_feed_forward=chunk_size_feed_forward,
            problem_type=problem_type,
            is_encoder_decoder=is_encoder_decoder,
            id2label=normalized_id2label,
            label2id=label2id,
            dtype=torch_dtype or dtype,
        )
        # Transformers v5 keeps model-specific token/vocabulary fields on the
        # subclass; only the common fields above belong in the base call.
        self.vocab_size = vocab_size
        self.bos_token_id = bos_token_id
        self.eos_token_id = eos_token_id
        self.pad_token_id = pad_token_id
        self.tie_word_embeddings = tie_word_embeddings
        if output_attentions is not None:
            self.output_attentions = output_attentions
        if task_specific_params is not None:
            self.task_specific_params = task_specific_params
        self.name_or_path = name_or_path if _name_or_path is None else _name_or_path
        self._commit_hash = _commit_hash
        self._attn_implementation = attn_implementation
        # Transformers v5 passes legacy and model-specific fields through the
        # tolerant config-loading path; preserve them as ordinary attributes.
        for key, value in kwargs.items():
            setattr(self, key, value)

__init__

__init__(
    vocab_size: int = 0,
    max_position_embeddings: int | None = None,
    d_model: int = 512,
    encoder_layers: int = 8,
    decoder_layers: int | None = None,
    encoder_attention_heads: int = 8,
    decoder_attention_heads: int | None = None,
    dropout: float = 0.1,
    dim_feedforward: int | None = None,
    share_embedding: bool = True,
    dataset: DatasetName | str = DatasetName.rico25,
    task: LayoutFormerPPTask
    | ConditionType
    | str = LayoutFormerPPTask.gen_t,
    max_num_elements: int = 20,
    bbox_format: BoxFormat | str = BoxFormat.ltwh,
    default_box_format: BoxFormat | str = BoxFormat.xywh,
    discrete_x_grid: int = 128,
    discrete_y_grid: int = 128,
    add_sep_token: bool = True,
    sort_by_dict: bool = True,
    add_task_embedding: bool = False,
    add_task_prompt_token_in_model: bool = False,
    num_task_prompt_token: int = 1,
    task_id: int | None = None,
    decode_max_length: int | None = None,
    eval_seed: int | None = None,
    gen_t_add_unk_token: bool = False,
    gen_ts_add_unk_token: bool = False,
    gen_r_add_unk_token: bool = False,
    gen_r_compact: bool = False,
    bos_token_id: int = 0,
    eos_token_id: int = 1,
    pad_token_id: int = 2,
    is_encoder_decoder: bool = True,
    condition_type: ConditionType | str | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    architectures: list[str] | None = None,
    output_hidden_states: bool | None = False,
    output_attentions: bool | None = False,
    return_dict: bool | None = True,
    chunk_size_feed_forward: int = 0,
    problem_type: Literal[
        "regression",
        "single_label_classification",
        "multi_label_classification",
    ]
    | None = None,
    id2label: dict[int | str, str] | None = None,
    label2id: dict[str, int] | None = None,
    label_translation_metadata: LabelTranslationMetadata
    | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    tie_word_embeddings: bool = True,
    task_specific_params: dict[
        str,
        str
        | int
        | float
        | bool
        | list[str | int | float | bool],
    ]
    | None = None,
    name_or_path: str = "",
    _name_or_path: str | None = None,
    _commit_hash: str | None = None,
    attn_implementation: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize architecture and task-specific generation defaults.

Source code in models/layoutformerpp/src/layoutformerpp/configuration_layoutformerpp.py
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
def __init__(
    self,
    vocab_size: int = 0,
    max_position_embeddings: int | None = None,
    d_model: int = 512,
    encoder_layers: int = 8,
    decoder_layers: int | None = None,
    encoder_attention_heads: int = 8,
    decoder_attention_heads: int | None = None,
    dropout: float = 0.1,
    dim_feedforward: int | None = None,
    share_embedding: bool = True,
    dataset: DatasetName | str = DatasetName.rico25,
    task: LayoutFormerPPTask | ConditionType | str = LayoutFormerPPTask.gen_t,
    max_num_elements: int = 20,
    bbox_format: BoxFormat | str = BoxFormat.ltwh,
    default_box_format: BoxFormat | str = BoxFormat.xywh,
    discrete_x_grid: int = 128,
    discrete_y_grid: int = 128,
    add_sep_token: bool = True,
    sort_by_dict: bool = True,
    add_task_embedding: bool = False,
    add_task_prompt_token_in_model: bool = False,
    num_task_prompt_token: int = 1,
    task_id: int | None = None,
    decode_max_length: int | None = None,
    eval_seed: int | None = None,
    gen_t_add_unk_token: bool = False,
    gen_ts_add_unk_token: bool = False,
    gen_r_add_unk_token: bool = False,
    gen_r_compact: bool = False,
    bos_token_id: int = 0,
    eos_token_id: int = 1,
    pad_token_id: int = 2,
    is_encoder_decoder: bool = True,
    condition_type: ConditionType | str | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    architectures: list[str] | None = None,
    output_hidden_states: bool | None = False,
    output_attentions: bool | None = False,
    return_dict: bool | None = True,
    chunk_size_feed_forward: int = 0,
    problem_type: Literal[
        "regression", "single_label_classification", "multi_label_classification"
    ]
    | None = None,
    id2label: dict[int | str, str] | None = None,
    label2id: dict[str, int] | None = None,
    label_translation_metadata: LabelTranslationMetadata | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    tie_word_embeddings: bool = True,
    task_specific_params: dict[
        str, str | int | float | bool | list[str | int | float | bool]
    ]
    | None = None,
    name_or_path: str = "",
    _name_or_path: str | None = None,
    _commit_hash: str | None = None,
    attn_implementation: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize architecture and task-specific generation defaults."""
    _ = (condition_type, model_type, transformers_version)
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    translation = label_translation_for_dataset(normalized_dataset)
    canonical_id2label = dict(translation.public_id2label)
    if (
        id2label is not None
        and {int(key): str(value) for key, value in id2label.items()}
        != canonical_id2label
    ):
        raise ValueError("id2label does not match the canonical dataset map")

    normalized_id2label: dict[int, str] = {
        key: value for key, value in canonical_id2label.items()
    }
    label2id = {value: key for key, value in canonical_id2label.items()}
    self.label_translation_metadata = (
        translation.metadata()
        if label_translation_metadata is None
        else validate_label_translation_metadata(
            label_translation_metadata,
            translation,
        )
    )
    condition_type = TASK_TO_CONDITION[normalized_task]
    self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
    self.task = str(normalized_task)
    self.condition_type = str(condition_type)
    defaults = TASK_DEFAULTS.get((normalized_dataset, condition_type), {})
    if max_position_embeddings is None:
        max_position_embeddings = int(defaults.get("max_position_embeddings", 150))
    if decode_max_length is None:
        decode_max_length = int(defaults.get("decode_max_length", 120))
    if eval_seed is None:
        eval_seed = int(defaults.get("eval_seed", 100))

    self.vocab_size = vocab_size
    self.max_position_embeddings = max_position_embeddings
    self.d_model = d_model
    self.encoder_layers = encoder_layers
    self.decoder_layers = (
        decoder_layers if decoder_layers is not None else encoder_layers
    )
    self.encoder_attention_heads = encoder_attention_heads
    self.decoder_attention_heads = (
        decoder_attention_heads
        if decoder_attention_heads is not None
        else encoder_attention_heads
    )
    self.dropout = dropout
    self.dim_feedforward = (
        dim_feedforward if dim_feedforward is not None else d_model * 4
    )
    self.share_embedding = share_embedding
    self.max_num_elements = max_num_elements
    self.bbox_format = str(normalize_box_format(bbox_format))
    self.default_box_format = str(normalize_box_format(default_box_format))

    self.discrete_x_grid = discrete_x_grid
    self.discrete_y_grid = discrete_y_grid
    self.add_sep_token = add_sep_token
    self.sort_by_dict = sort_by_dict
    self.add_task_embedding = add_task_embedding
    self.add_task_prompt_token_in_model = add_task_prompt_token_in_model
    self.num_task_prompt_token = num_task_prompt_token
    self.task_id = task_id

    self.decode_max_length = decode_max_length
    self.eval_seed = eval_seed
    self.gen_t_add_unk_token = gen_t_add_unk_token
    self.gen_ts_add_unk_token = gen_ts_add_unk_token
    self.gen_r_add_unk_token = gen_r_add_unk_token
    self.gen_r_compact = gen_r_compact

    super().__init__(
        transformers_version=transformers_version,
        architectures=architectures,
        output_hidden_states=output_hidden_states,
        return_dict=return_dict,
        chunk_size_feed_forward=chunk_size_feed_forward,
        problem_type=problem_type,
        is_encoder_decoder=is_encoder_decoder,
        id2label=normalized_id2label,
        label2id=label2id,
        dtype=torch_dtype or dtype,
    )
    # Transformers v5 keeps model-specific token/vocabulary fields on the
    # subclass; only the common fields above belong in the base call.
    self.vocab_size = vocab_size
    self.bos_token_id = bos_token_id
    self.eos_token_id = eos_token_id
    self.pad_token_id = pad_token_id
    self.tie_word_embeddings = tie_word_embeddings
    if output_attentions is not None:
        self.output_attentions = output_attentions
    if task_specific_params is not None:
        self.task_specific_params = task_specific_params
    self.name_or_path = name_or_path if _name_or_path is None else _name_or_path
    self._commit_hash = _commit_hash
    self._attn_implementation = attn_implementation
    # Transformers v5 passes legacy and model-specific fields through the
    # tolerant config-loading path; preserve them as ordinary attributes.
    for key, value in kwargs.items():
        setattr(self, key, value)

LayoutFormerPPForConditionalGeneration

Bases: PreTrainedModel

Transformers PreTrainedModel with checkpoint-compatible module names.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
class LayoutFormerPPForConditionalGeneration(PreTrainedModel):
    """Transformers `PreTrainedModel` with checkpoint-compatible module names."""

    config_class = LayoutFormerPPConfig
    base_model_prefix = "layoutformerpp"
    main_input_name = "input_ids"
    _tied_weights_keys = {
        "dec_embedding.weight": "enc_embedding.weight",
        "out.weight": "dec_embedding.weight",
    }

    def __init__(self, config: LayoutFormerPPConfig) -> None:
        """Initialize checkpoint-compatible encoder/decoder modules."""
        super().__init__(config)
        self.d_model = config.d_model
        self.vocab_size = config.vocab_size
        self.bos_token_id = int(config.bos_token_id)
        self.pad_token_id = int(config.pad_token_id)
        self.eos_token_id = int(config.eos_token_id)

        self.enc_embedding = nn.Embedding(config.vocab_size, config.d_model)
        self.enc_pos_embedding = PositionalEncoding(
            config.d_model, config.dropout, max_len=config.max_position_embeddings
        )
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=config.d_model,
            nhead=config.encoder_attention_heads,
            dropout=config.dropout,
            dim_feedforward=config.dim_feedforward,
        )
        self.encoder = nn.TransformerEncoder(
            encoder_layer, num_layers=config.encoder_layers
        )

        self.dec_embedding = (
            self.enc_embedding
            if config.share_embedding
            else nn.Embedding(config.vocab_size, config.d_model)
        )
        self.dec_pos_embedding = PositionalEncoding(
            config.d_model, config.dropout, max_len=config.max_position_embeddings
        )
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=config.d_model,
            nhead=config.decoder_attention_heads,
            dropout=config.dropout,
            dim_feedforward=config.dim_feedforward,
        )
        self.decoder = nn.TransformerDecoder(
            decoder_layer, num_layers=config.decoder_layers
        )
        self.out = nn.Linear(config.d_model, config.vocab_size, bias=False)
        self.out.weight = self.dec_embedding.weight

        self.task_embedding = None
        if config.add_task_embedding:
            self.task_embedding = nn.Embedding(6, config.d_model)

        # Preserve the reference's process-wide backtracking stream for full-split parity.
        self._backtracking_generator: torch.Generator | None = None
        self._backtracking_generator_state: bytes | None = None
        self._backtracking_random: random.Random | None = None

        self.task_prompt_embed = None
        if config.add_task_prompt_token_in_model:
            self.num_task_prompt_token = config.num_task_prompt_token
            self.task_prompt_embed = nn.Parameter(
                torch.empty(6, config.num_task_prompt_token, config.d_model)
            )
            nn.init.normal_(self.task_prompt_embed)
        self.tie_weights()
        self.all_tied_weights_keys = dict(self._tied_weights_keys)

    def encode(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        padding_mask: Bool[torch.Tensor, "batch tokens"],
        task_ids: Int[torch.Tensor, "batch"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "seq batch channels"],
        Bool[torch.Tensor, "batch seq"],
    ]:
        """Encode input token ids with optional task prompt embeddings."""
        if self.task_prompt_embed is not None:
            if task_ids is None:
                raise ValueError(
                    "task_ids are required when task prompt embeddings are enabled"
                )

            x = self.enc_embedding(input_ids)
            prompts = self.task_prompt_embed[task_ids]
            x = torch.cat([prompts, x], dim=1).permute(1, 0, 2)
            bsz = input_ids.size(0)
            prompt_mask = padding_mask.new_zeros(
                (bsz, self.num_task_prompt_token)
            ).bool()
            enc_padding_mask = torch.cat([prompt_mask, padding_mask], dim=1)
        else:
            x = self.enc_embedding(input_ids).permute(1, 0, 2)
            enc_padding_mask = padding_mask
        enc_hs = self.encoder(
            self.enc_pos_embedding(x), src_key_padding_mask=enc_padding_mask
        )
        if self.task_embedding is not None:
            if task_ids is None:
                raise ValueError(
                    "task_ids are required when task embeddings are enabled"
                )

            enc_hs = enc_hs + self.task_embedding(task_ids).unsqueeze(0)
        return enc_hs, enc_padding_mask

    def prepare_decoder_input_ids_from_labels(
        self, labels: Int[torch.Tensor, "batch tokens"]
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Shift labels right and prepend BOS."""
        bos = labels.new_full((labels.size(0), 1), self.bos_token_id)
        return torch.cat([bos, labels[:, :-1]], dim=1)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        labels: Int[torch.Tensor, "batch tokens"] | None = None,
        decoder_input_ids: Int[torch.Tensor, "batch tokens"] | None = None,
        task_ids: Int[torch.Tensor, "batch"] | None = None,
        return_dict: bool | None = None,
    ) -> Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]:
        """Run teacher-forced LayoutFormer++ decoding."""
        if attention_mask is None:
            attention_mask = input_ids.ne(self.pad_token_id)
        padding_mask = ~attention_mask.bool()
        if decoder_input_ids is None:
            if labels is None:
                raise ValueError("decoder_input_ids or labels must be provided")

            decoder_input_ids = self.prepare_decoder_input_ids_from_labels(labels)
        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        dec_input = self.dec_pos_embedding(
            self.dec_embedding(decoder_input_ids).permute(1, 0, 2)
        )
        tgt_mask = generate_square_subsequent_mask(dec_input.size(0), dec_input.device)
        y = self.decoder(
            tgt=dec_input,
            memory=enc_hs,
            tgt_mask=tgt_mask,
            memory_key_padding_mask=enc_padding_mask,
        )
        logits = self.out(y.permute(1, 0, 2))
        loss = None
        if labels is not None:
            targets = labels.clone()
            targets[targets == self.pad_token_id] = -100
            loss = F.cross_entropy(
                logits.reshape(-1, logits.size(-1)),
                targets.reshape(-1),
                ignore_index=-100,
            )
        if return_dict is False:
            return (logits,) if loss is None else (loss, logits)
        return Seq2SeqLMOutput(loss=cast(torch.FloatTensor | None, loss), logits=logits)

    @torch.no_grad()
    def _generate_sequences(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        *,
        max_length: int | None = None,
        do_sample: bool = False,
        top_k: int = 10,
        temperature: float = 0.7,
        constrained_decoding: bool = False,
        generation_constraint_fn: Callable[
            [int, int, Int[torch.Tensor, "tokens"]], tuple[list[int], int | None]
        ]
        | None = None,
        task_ids: Int[torch.Tensor, "batch"] | None = None,
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Run the reference greedy/top-k autoregressive loop."""
        if attention_mask is None:
            attention_mask = input_ids.ne(self.pad_token_id)
        padding_mask = ~attention_mask.bool()
        max_length = max_length or self.config.decode_max_length
        if constrained_decoding:
            return self._generate_constrained_sequences(
                input_ids,
                padding_mask,
                max_length=max_length,
                top_k=top_k,
                temperature=temperature,
                generation_constraint_fn=generation_constraint_fn,
                task_ids=task_ids,
                generator=generator,
            )

        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        bsz = input_ids.size(0)
        stop = input_ids.new_zeros(bsz, dtype=torch.bool)
        pred_ids = input_ids.new_full((bsz, 1), self.bos_token_id)
        outs: list[Int[torch.Tensor, "batch"]] = []
        for idx in range(max_length):
            dec_input = self.dec_pos_embedding(
                self.dec_embedding(pred_ids).permute(1, 0, 2)
            )
            tgt_mask = generate_square_subsequent_mask(idx + 1, input_ids.device)
            y = self.decoder(
                tgt=dec_input,
                memory=enc_hs,
                tgt_mask=tgt_mask,
                memory_key_padding_mask=enc_padding_mask,
            )
            logits = self.out(y.permute(1, 0, 2)[:, -1, :])
            if generation_constraint_fn is not None:
                current = (
                    torch.stack(outs, dim=1) if outs else input_ids.new_empty((bsz, 0))
                )
                for batch_idx in range(bsz):
                    allowed, _ = generation_constraint_fn(
                        batch_idx, idx, current[batch_idx]
                    )
                    mask = torch.ones(
                        logits.size(-1), dtype=torch.bool, device=logits.device
                    )
                    mask[allowed] = False
                    logits[batch_idx].masked_fill_(mask, -math.inf)
            if do_sample:
                probs = F.softmax(top_k_logits(logits / temperature, top_k), dim=-1)
                curr = multinomial(
                    probs,
                    num_samples=1,
                    generator=generator,
                    device=probs.device,
                ).squeeze(-1)
            else:
                curr = torch.argmax(logits, dim=-1)
            eos = curr.eq(self.eos_token_id)
            curr[stop] = self.pad_token_id
            outs.append(curr)
            pred_ids = torch.cat([pred_ids, curr.unsqueeze(1)], dim=1)
            stop = torch.logical_or(stop, eos)
            if bool(torch.all(stop)):
                break
        return torch.stack(outs, dim=1)

    def _generate_constrained_sequences(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        padding_mask: Bool[torch.Tensor, "batch tokens"],
        *,
        max_length: int,
        top_k: int,
        temperature: float,
        generation_constraint_fn: Callable[
            [int, int, Int[torch.Tensor, "tokens"]], tuple[list[int], int | None]
        ]
        | None,
        task_ids: Int[torch.Tensor, "batch"] | None,
        generator: torch.Generator | None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Generate using a probability gate and bounded token backtracking."""
        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        batch_size = input_ids.size(0)
        stop = input_ids.new_zeros(batch_size, dtype=torch.bool)
        pred_ids = input_ids.new_full((batch_size, 1), self.bos_token_id)
        outs: list[Int[torch.Tensor, "batch"]] = []
        retry_steps: list[int] = []
        backtrack_rng = _backtracking_rng(
            generator,
            previous_generator=self._backtracking_generator,
            previous_state=self._backtracking_generator_state,
            previous_rng=self._backtracking_random,
        )
        backtrack_temperature = 1.5
        backtrack_flag = False
        idx = 0

        while idx < max_length:
            dec_input = self.dec_pos_embedding(
                self.dec_embedding(pred_ids).permute(1, 0, 2)
            )
            tgt_mask = generate_square_subsequent_mask(idx + 1, input_ids.device)
            y = self.decoder(
                tgt=dec_input,
                memory=enc_hs,
                tgt_mask=tgt_mask,
                memory_key_padding_mask=enc_padding_mask,
            )
            logits = self.out(y.permute(1, 0, 2)[:, -1, :])
            probabilities = F.softmax(logits, dim=-1)
            back_idx = None

            if generation_constraint_fn is not None:
                current = (
                    torch.stack(outs, dim=1)
                    if outs
                    else input_ids.new_empty((batch_size, 0))
                )
                for batch_idx in range(batch_size):
                    allowed, back_idx = generation_constraint_fn(
                        batch_idx, idx, current[batch_idx]
                    )
                    mask = torch.ones(
                        logits.size(-1), dtype=torch.bool, device=logits.device
                    )
                    mask[allowed] = False
                    logits[batch_idx].masked_fill_(mask, -math.inf)

            gated_logits, has_candidate = _probability_gate(
                logits, probabilities, threshold=0.3
            )
            randint = (
                backtrack_rng.randint if backtrack_rng is not None else random.randint
            )
            rollback = _backtracking_index(
                step=idx,
                constraint_index=back_idx,
                has_candidate=has_candidate,
                retry_count=retry_steps.count(idx),
                backtrack_pending=backtrack_flag,
                randint=randint,
            )
            if rollback is not None:
                retry_steps.append(idx)
                idx = rollback
                outs = outs[:idx]
                pred_ids = pred_ids[:, : idx + 1]
                backtrack_flag = True
                continue

            if has_candidate:
                logits[0] = gated_logits[0]

            decode_temperature = (
                backtrack_temperature if backtrack_flag else temperature
            )
            backtrack_flag = False
            probabilities = F.softmax(
                top_k_logits(logits / decode_temperature, top_k), dim=-1
            )
            curr = multinomial(
                probabilities,
                num_samples=1,
                generator=generator,
                device=probabilities.device,
            ).squeeze(-1)
            eos = curr.eq(self.eos_token_id)
            curr[stop] = self.pad_token_id
            outs.append(curr)
            pred_ids = torch.cat([pred_ids, curr.unsqueeze(1)], dim=1)
            stop = torch.logical_or(stop, eos)
            if bool(torch.all(stop)):
                break

            idx += 1

        self._backtracking_generator = generator
        self._backtracking_generator_state = (
            None if generator is None else bytes(generator.get_state().tolist())
        )
        self._backtracking_random = backtrack_rng

        return (
            torch.stack(outs, dim=1) if outs else input_ids.new_empty((batch_size, 0))
        )

__init__

__init__(config: LayoutFormerPPConfig) -> None

Initialize checkpoint-compatible encoder/decoder modules.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
def __init__(self, config: LayoutFormerPPConfig) -> None:
    """Initialize checkpoint-compatible encoder/decoder modules."""
    super().__init__(config)
    self.d_model = config.d_model
    self.vocab_size = config.vocab_size
    self.bos_token_id = int(config.bos_token_id)
    self.pad_token_id = int(config.pad_token_id)
    self.eos_token_id = int(config.eos_token_id)

    self.enc_embedding = nn.Embedding(config.vocab_size, config.d_model)
    self.enc_pos_embedding = PositionalEncoding(
        config.d_model, config.dropout, max_len=config.max_position_embeddings
    )
    encoder_layer = nn.TransformerEncoderLayer(
        d_model=config.d_model,
        nhead=config.encoder_attention_heads,
        dropout=config.dropout,
        dim_feedforward=config.dim_feedforward,
    )
    self.encoder = nn.TransformerEncoder(
        encoder_layer, num_layers=config.encoder_layers
    )

    self.dec_embedding = (
        self.enc_embedding
        if config.share_embedding
        else nn.Embedding(config.vocab_size, config.d_model)
    )
    self.dec_pos_embedding = PositionalEncoding(
        config.d_model, config.dropout, max_len=config.max_position_embeddings
    )
    decoder_layer = nn.TransformerDecoderLayer(
        d_model=config.d_model,
        nhead=config.decoder_attention_heads,
        dropout=config.dropout,
        dim_feedforward=config.dim_feedforward,
    )
    self.decoder = nn.TransformerDecoder(
        decoder_layer, num_layers=config.decoder_layers
    )
    self.out = nn.Linear(config.d_model, config.vocab_size, bias=False)
    self.out.weight = self.dec_embedding.weight

    self.task_embedding = None
    if config.add_task_embedding:
        self.task_embedding = nn.Embedding(6, config.d_model)

    # Preserve the reference's process-wide backtracking stream for full-split parity.
    self._backtracking_generator: torch.Generator | None = None
    self._backtracking_generator_state: bytes | None = None
    self._backtracking_random: random.Random | None = None

    self.task_prompt_embed = None
    if config.add_task_prompt_token_in_model:
        self.num_task_prompt_token = config.num_task_prompt_token
        self.task_prompt_embed = nn.Parameter(
            torch.empty(6, config.num_task_prompt_token, config.d_model)
        )
        nn.init.normal_(self.task_prompt_embed)
    self.tie_weights()
    self.all_tied_weights_keys = dict(self._tied_weights_keys)

encode

encode(
    input_ids: Int[Tensor, "batch tokens"],
    padding_mask: Bool[Tensor, "batch tokens"],
    task_ids: Int[Tensor, "batch"] | None = None,
) -> tuple[
    Float[torch.Tensor, "seq batch channels"],
    Bool[torch.Tensor, "batch seq"],
]

Encode input token ids with optional task prompt embeddings.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.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
def encode(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    padding_mask: Bool[torch.Tensor, "batch tokens"],
    task_ids: Int[torch.Tensor, "batch"] | None = None,
) -> tuple[
    Float[torch.Tensor, "seq batch channels"],
    Bool[torch.Tensor, "batch seq"],
]:
    """Encode input token ids with optional task prompt embeddings."""
    if self.task_prompt_embed is not None:
        if task_ids is None:
            raise ValueError(
                "task_ids are required when task prompt embeddings are enabled"
            )

        x = self.enc_embedding(input_ids)
        prompts = self.task_prompt_embed[task_ids]
        x = torch.cat([prompts, x], dim=1).permute(1, 0, 2)
        bsz = input_ids.size(0)
        prompt_mask = padding_mask.new_zeros(
            (bsz, self.num_task_prompt_token)
        ).bool()
        enc_padding_mask = torch.cat([prompt_mask, padding_mask], dim=1)
    else:
        x = self.enc_embedding(input_ids).permute(1, 0, 2)
        enc_padding_mask = padding_mask
    enc_hs = self.encoder(
        self.enc_pos_embedding(x), src_key_padding_mask=enc_padding_mask
    )
    if self.task_embedding is not None:
        if task_ids is None:
            raise ValueError(
                "task_ids are required when task embeddings are enabled"
            )

        enc_hs = enc_hs + self.task_embedding(task_ids).unsqueeze(0)
    return enc_hs, enc_padding_mask

prepare_decoder_input_ids_from_labels

prepare_decoder_input_ids_from_labels(
    labels: Int[Tensor, "batch tokens"],
) -> Int[torch.Tensor, "batch tokens"]

Shift labels right and prepend BOS.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
225
226
227
228
229
230
def prepare_decoder_input_ids_from_labels(
    self, labels: Int[torch.Tensor, "batch tokens"]
) -> Int[torch.Tensor, "batch tokens"]:
    """Shift labels right and prepend BOS."""
    bos = labels.new_full((labels.size(0), 1), self.bos_token_id)
    return torch.cat([bos, labels[:, :-1]], dim=1)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    attention_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    labels: Int[Tensor, "batch tokens"] | None = None,
    decoder_input_ids: Int[Tensor, "batch tokens"]
    | None = None,
    task_ids: Int[Tensor, "batch"] | None = None,
    return_dict: bool | None = None,
) -> (
    Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]
)

Run teacher-forced LayoutFormer++ decoding.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    labels: Int[torch.Tensor, "batch tokens"] | None = None,
    decoder_input_ids: Int[torch.Tensor, "batch tokens"] | None = None,
    task_ids: Int[torch.Tensor, "batch"] | None = None,
    return_dict: bool | None = None,
) -> Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]:
    """Run teacher-forced LayoutFormer++ decoding."""
    if attention_mask is None:
        attention_mask = input_ids.ne(self.pad_token_id)
    padding_mask = ~attention_mask.bool()
    if decoder_input_ids is None:
        if labels is None:
            raise ValueError("decoder_input_ids or labels must be provided")

        decoder_input_ids = self.prepare_decoder_input_ids_from_labels(labels)
    enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
    dec_input = self.dec_pos_embedding(
        self.dec_embedding(decoder_input_ids).permute(1, 0, 2)
    )
    tgt_mask = generate_square_subsequent_mask(dec_input.size(0), dec_input.device)
    y = self.decoder(
        tgt=dec_input,
        memory=enc_hs,
        tgt_mask=tgt_mask,
        memory_key_padding_mask=enc_padding_mask,
    )
    logits = self.out(y.permute(1, 0, 2))
    loss = None
    if labels is not None:
        targets = labels.clone()
        targets[targets == self.pad_token_id] = -100
        loss = F.cross_entropy(
            logits.reshape(-1, logits.size(-1)),
            targets.reshape(-1),
            ignore_index=-100,
        )
    if return_dict is False:
        return (logits,) if loss is None else (loss, logits)
    return Seq2SeqLMOutput(loss=cast(torch.FloatTensor | None, loss), logits=logits)

LayoutFormerPPPipeline

Bases: LayoutGenerationPipeline

Compose a LayoutFormer++ model and processor for layout generation.

Parameters:

Name Type Description Default
model LayoutFormerPPForConditionalGeneration

Converted LayoutFormer++ model.

required
processor LayoutFormerPPProcessor

Matching processor/tokenizer.

required
config LayoutFormerPPConfig | None

Optional root pipeline config. Defaults to model.config.

None

Examples:

>>> processor = LayoutFormerPPProcessor.from_config(dataset="rico", task="gen_t")
>>> config = LayoutFormerPPConfig(vocab_size=processor.tokenizer.vocab_size)
>>> pipe = LayoutFormerPPPipeline(
...     model=LayoutFormerPPForConditionalGeneration(config),
...     processor=processor,
... )
>>> pipe.config.model_type
'layoutformerpp'
Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
 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
class LayoutFormerPPPipeline(LayoutGenerationPipeline):
    """Compose a LayoutFormer++ model and processor for layout generation.

    Args:
        model: Converted LayoutFormer++ model.
        processor: Matching processor/tokenizer.
        config: Optional root pipeline config. Defaults to `model.config`.

    Examples:
        >>> processor = LayoutFormerPPProcessor.from_config(dataset="rico", task="gen_t")
        >>> config = LayoutFormerPPConfig(vocab_size=processor.tokenizer.vocab_size)
        >>> pipe = LayoutFormerPPPipeline(
        ...     model=LayoutFormerPPForConditionalGeneration(config),
        ...     processor=processor,
        ... )
        >>> pipe.config.model_type
        'layoutformerpp'
    """

    config_class: ClassVar[type[PretrainedConfig]] = LayoutFormerPPConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = (
        model_processor_component_specs(
            model_loader=_load_model_component,
            processor_loader=_load_processor_component,
        )
    )

    config: LayoutFormerPPConfig
    model: LayoutFormerPPForConditionalGeneration
    processor: LayoutFormerPPProcessor

    def __init__(
        self,
        model: LayoutFormerPPForConditionalGeneration,
        processor: LayoutFormerPPProcessor,
        config: LayoutFormerPPConfig | None = None,
    ) -> None:
        """Initialize the pipeline with model and processor components."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> LayoutFormerPPPipeline:
        """Build a pipeline from loaded root components."""
        return cls(
            config=cast(LayoutFormerPPConfig, config),
            model=cast(LayoutFormerPPForConditionalGeneration, components["model"]),
            processor=cast(LayoutFormerPPProcessor, components["processor"]),
        )

    @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: list[list[int | str]]
        | Int[torch.Tensor, "batch elements"]
        | None = None,
        bbox: LayoutFormerPPBBoxInput = None,
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        relations: list[list[tuple[int, int, int, int, int]]]
        | Int[torch.Tensor, "batch relations relation_attrs"]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
        max_length: int | None = None,
        do_sample: bool | None = None,
        top_k: int = 10,
        temperature: float = 0.7,
    ) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:  # ty: ignore[invalid-method-override]
        """Generate layouts by encoding conditions, generating ids, and decoding.

        Args:
            batch_size: Number of layouts to generate when labels are omitted.
            seed: Convenience seed used only when `generator` is absent.
            generator: Optional PyTorch generator; takes precedence over `seed`.
            condition_type: Canonical condition type or supported alias.
            labels: Optional label conditions.
            bbox: Optional layout boxes for size/completion/refinement conditions.
            mask: Reserved public validity mask input.
            relations: Relation tuples use one-based indexes within each label. For
                non-canvas sources, the source element must precede its target in the
                label sequence used for decoding.
            num_elements: Reserved v1 interface argument.
            box_format: Input and output bounding-box format.
            normalized: Whether public boxes are normalized.
            canvas_size: Reserved v1 interface argument.
            num_inference_steps: Reserved v1 interface argument.
            output_type: Return `dataclass` or `dict`.
            return_intermediates: Reserved output detail flag.
            max_length: Optional token decode length override.
            do_sample: Optional sampling override.
            top_k: Top-k value used by the reference sampling loop.
            temperature: Sampling temperature.

        When ``max_length`` is omitted and the model config matches its dataset/task
        default, the pipeline uses 120 tokens for unconditional, completion, label,
        and label-size generation, 150 for relation generation, or input length plus
        one for refinement. This equality rule also applies when the config value was
        set explicitly; pass ``max_length`` to select another per-call limit.

        For batches larger than one, the probability gate uses row zero, relation
        rollback uses the last row's rollback point, and a rollback truncates every
        row to the same prefix.

        Returns:
            Layout generation output dataclass or dictionary.

        Raises:
            ValueError: If processor inputs or relation label/index references are invalid.
        """
        _ = (num_elements, num_inference_steps, return_intermediates)
        encoded = self.processor(
            condition_type=condition_type,
            batch_size=batch_size,
            return_tensors="pt",
            labels=labels,
            bbox=bbox,
            mask=mask,
            relations=relations,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        model_device = next(self.model.parameters()).device
        input_ids = encoded["input_ids"].to(model_device)
        attention_mask = encoded["attention_mask"].to(model_device)
        condition = self.processor.normalize_condition_type(condition_type)
        generation_constraint_fn = None
        if condition in {
            ConditionType.label,
            ConditionType.label_size,
            ConditionType.relation,
            ConditionType.refinement,
        }:
            constraint_batch_size = input_ids.size(0)
            row_lengths = (
                [len(row) for row in labels]
                if labels is not None
                else [0] * constraint_batch_size
            )
            prepared_mask = self.processor._prepare_mask(
                mask,
                batch_size=constraint_batch_size,
                row_lengths=row_lengths,
            )
            constraint_labels = self.processor._prepare_labels(
                labels,
                constraint_batch_size,
                prepared_mask,
            )
            constraint_bboxes = self.processor._prepare_bbox(
                bbox,
                labels=constraint_labels,
                box_format=box_format,
                mask=prepared_mask,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            generation_constraint_fn = LayoutFormerPPGenerationConstraint(
                tokenizer=self.processor.tokenizer,
                condition=condition,
                labels=constraint_labels,
                bboxes=constraint_bboxes,
                relations=relations,
                x_grid=self.processor.x_grid,
                y_grid=self.processor.y_grid,
                add_sep_token=self.processor.add_sep_token,
            )

        generation_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
        )
        default_do_sample = condition in {
            ConditionType.unconditional,
            ConditionType.completion,
        }
        model_task = normalize_layoutformerpp_task(self.model.config.task)
        config_default_max_length = TASK_DEFAULTS[
            (
                normalize_layoutformerpp_dataset(self.model.config.dataset),
                TASK_TO_CONDITION[model_task],
            )
        ]["decode_max_length"]
        default_max_length = self.model.config.decode_max_length
        if default_max_length == config_default_max_length:
            default_max_length = 150 if condition is ConditionType.relation else 120
            if condition is ConditionType.refinement:
                default_max_length = input_ids.size(-1) + 1

        sequences = self.model._generate_sequences(
            input_ids,
            attention_mask,
            max_length=default_max_length if max_length is None else max_length,
            do_sample=default_do_sample if do_sample is None else do_sample,
            top_k=top_k,
            temperature=temperature,
            constrained_decoding=(
                condition
                in {
                    ConditionType.label,
                    ConditionType.label_size,
                    ConditionType.relation,
                }
                and (do_sample is None or do_sample)
            ),
            generation_constraint_fn=generation_constraint_fn,
            generator=generation_generator,
        )
        return self.processor.post_process_layouts(
            sequences.cpu(),
            box_format=box_format,
            output_type=output_type,
        )

__init__

__init__(
    model: LayoutFormerPPForConditionalGeneration,
    processor: LayoutFormerPPProcessor,
    config: LayoutFormerPPConfig | None = None,
) -> None

Initialize the pipeline with model and processor components.

Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
110
111
112
113
114
115
116
117
118
119
120
def __init__(
    self,
    model: LayoutFormerPPForConditionalGeneration,
    processor: LayoutFormerPPProcessor,
    config: LayoutFormerPPConfig | None = None,
) -> None:
    """Initialize the pipeline with model and processor components."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor

__call__

__call__(
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[Tensor, "batch elements"] | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[Tensor, "batch relations relation_attrs"]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    max_length: int | None = None,
    do_sample: bool | None = None,
    top_k: int = 10,
    temperature: float = 0.7,
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict

Generate layouts by encoding conditions, generating ids, and decoding.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate when labels are omitted.

1
seed int | None

Convenience seed used only when generator is absent.

None
generator Generator | None

Optional PyTorch generator; takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition type or supported alias.

unconditional
labels list[list[int | str]] | Int[Tensor, 'batch elements'] | None

Optional label conditions.

None
bbox LayoutFormerPPBBoxInput

Optional layout boxes for size/completion/refinement conditions.

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

Reserved public validity mask input.

None
relations list[list[tuple[int, int, int, int, int]]] | Int[Tensor, 'batch relations relation_attrs'] | None

Relation tuples use one-based indexes within each label. For non-canvas sources, the source element must precede its target in the label sequence used for decoding.

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

Reserved v1 interface argument.

None
box_format BoxFormat | str

Input and output bounding-box format.

xywh
normalized bool

Whether public boxes are normalized.

True
canvas_size tuple[int, int] | None

Reserved v1 interface argument.

None
num_inference_steps int | None

Reserved v1 interface argument.

None
output_type OutputType | str

Return dataclass or dict.

dataclass
return_intermediates bool

Reserved output detail flag.

False
max_length int | None

Optional token decode length override.

None
do_sample bool | None

Optional sampling override.

None
top_k int

Top-k value used by the reference sampling loop.

10
temperature float

Sampling temperature.

0.7

When max_length is omitted and the model config matches its dataset/task default, the pipeline uses 120 tokens for unconditional, completion, label, and label-size generation, 150 for relation generation, or input length plus one for refinement. This equality rule also applies when the config value was set explicitly; pass max_length to select another per-call limit.

For batches larger than one, the probability gate uses row zero, relation rollback uses the last row's rollback point, and a rollback truncates every row to the same prefix.

Returns:

Type Description
LayoutGenerationOutput | LayoutFormerPPOutputDict

Layout generation output dataclass or dictionary.

Raises:

Type Description
ValueError

If processor inputs or relation label/index references are invalid.

Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
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
@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: list[list[int | str]]
    | Int[torch.Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[torch.Tensor, "batch relations relation_attrs"]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    max_length: int | None = None,
    do_sample: bool | None = None,
    top_k: int = 10,
    temperature: float = 0.7,
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:  # ty: ignore[invalid-method-override]
    """Generate layouts by encoding conditions, generating ids, and decoding.

    Args:
        batch_size: Number of layouts to generate when labels are omitted.
        seed: Convenience seed used only when `generator` is absent.
        generator: Optional PyTorch generator; takes precedence over `seed`.
        condition_type: Canonical condition type or supported alias.
        labels: Optional label conditions.
        bbox: Optional layout boxes for size/completion/refinement conditions.
        mask: Reserved public validity mask input.
        relations: Relation tuples use one-based indexes within each label. For
            non-canvas sources, the source element must precede its target in the
            label sequence used for decoding.
        num_elements: Reserved v1 interface argument.
        box_format: Input and output bounding-box format.
        normalized: Whether public boxes are normalized.
        canvas_size: Reserved v1 interface argument.
        num_inference_steps: Reserved v1 interface argument.
        output_type: Return `dataclass` or `dict`.
        return_intermediates: Reserved output detail flag.
        max_length: Optional token decode length override.
        do_sample: Optional sampling override.
        top_k: Top-k value used by the reference sampling loop.
        temperature: Sampling temperature.

    When ``max_length`` is omitted and the model config matches its dataset/task
    default, the pipeline uses 120 tokens for unconditional, completion, label,
    and label-size generation, 150 for relation generation, or input length plus
    one for refinement. This equality rule also applies when the config value was
    set explicitly; pass ``max_length`` to select another per-call limit.

    For batches larger than one, the probability gate uses row zero, relation
    rollback uses the last row's rollback point, and a rollback truncates every
    row to the same prefix.

    Returns:
        Layout generation output dataclass or dictionary.

    Raises:
        ValueError: If processor inputs or relation label/index references are invalid.
    """
    _ = (num_elements, num_inference_steps, return_intermediates)
    encoded = self.processor(
        condition_type=condition_type,
        batch_size=batch_size,
        return_tensors="pt",
        labels=labels,
        bbox=bbox,
        mask=mask,
        relations=relations,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    model_device = next(self.model.parameters()).device
    input_ids = encoded["input_ids"].to(model_device)
    attention_mask = encoded["attention_mask"].to(model_device)
    condition = self.processor.normalize_condition_type(condition_type)
    generation_constraint_fn = None
    if condition in {
        ConditionType.label,
        ConditionType.label_size,
        ConditionType.relation,
        ConditionType.refinement,
    }:
        constraint_batch_size = input_ids.size(0)
        row_lengths = (
            [len(row) for row in labels]
            if labels is not None
            else [0] * constraint_batch_size
        )
        prepared_mask = self.processor._prepare_mask(
            mask,
            batch_size=constraint_batch_size,
            row_lengths=row_lengths,
        )
        constraint_labels = self.processor._prepare_labels(
            labels,
            constraint_batch_size,
            prepared_mask,
        )
        constraint_bboxes = self.processor._prepare_bbox(
            bbox,
            labels=constraint_labels,
            box_format=box_format,
            mask=prepared_mask,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        generation_constraint_fn = LayoutFormerPPGenerationConstraint(
            tokenizer=self.processor.tokenizer,
            condition=condition,
            labels=constraint_labels,
            bboxes=constraint_bboxes,
            relations=relations,
            x_grid=self.processor.x_grid,
            y_grid=self.processor.y_grid,
            add_sep_token=self.processor.add_sep_token,
        )

    generation_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
    )
    default_do_sample = condition in {
        ConditionType.unconditional,
        ConditionType.completion,
    }
    model_task = normalize_layoutformerpp_task(self.model.config.task)
    config_default_max_length = TASK_DEFAULTS[
        (
            normalize_layoutformerpp_dataset(self.model.config.dataset),
            TASK_TO_CONDITION[model_task],
        )
    ]["decode_max_length"]
    default_max_length = self.model.config.decode_max_length
    if default_max_length == config_default_max_length:
        default_max_length = 150 if condition is ConditionType.relation else 120
        if condition is ConditionType.refinement:
            default_max_length = input_ids.size(-1) + 1

    sequences = self.model._generate_sequences(
        input_ids,
        attention_mask,
        max_length=default_max_length if max_length is None else max_length,
        do_sample=default_do_sample if do_sample is None else do_sample,
        top_k=top_k,
        temperature=temperature,
        constrained_decoding=(
            condition
            in {
                ConditionType.label,
                ConditionType.label_size,
                ConditionType.relation,
            }
            and (do_sample is None or do_sample)
        ),
        generation_constraint_fn=generation_constraint_fn,
        generator=generation_generator,
    )
    return self.processor.post_process_layouts(
        sequences.cpu(),
        box_format=box_format,
        output_type=output_type,
    )

LayoutFormerPPProcessor

Bases: ProcessorMixin

Build LayoutFormer++ text inputs and parse generated layouts.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
class LayoutFormerPPProcessor(ProcessorMixin):
    """Build LayoutFormer++ text inputs and parse generated layouts."""

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutFormerPPTokenizer"

    def __init__(
        self,
        tokenizer: LayoutFormerPPTokenizer,
        dataset: DatasetName | str = DEFAULT_DATASET,
        task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
        add_sep_token: bool = True,
        x_grid: int = 128,
        y_grid: int = 128,
        id2label: dict[int, str] | None = None,
        label_translation_metadata: LabelTranslationMetadata | None = None,
    ) -> None:
        """Initialize serializers, label maps, and tokenizer state."""
        self.tokenizer = tokenizer
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
        self.task = str(normalized_task)
        self.add_sep_token = add_sep_token
        self.x_grid = x_grid
        self.y_grid = y_grid
        labels = labels_for_dataset(normalized_dataset)
        public_id2label = (
            {int(key): str(value) for key, value in id2label.items()}
            if id2label is not None
            else dict(enumerate(labels))
        )
        canonical_translation = label_translation_for_dataset(normalized_dataset)
        if normalized_dataset is DatasetName.rico25 and public_id2label != dict(
            RICO25_LABEL_TRANSLATION.public_id2label
        ):
            raise ValueError("RICO25 public id2label must match the canonical map")

        translation = build_label_translation(
            public_id2label,
            canonical_translation.sequence_id2label,
        )
        self.id2label = dict(translation.public_id2label)
        self.public_id2label = dict(translation.public_id2label)
        self.public_label2id = {
            normalize_label_name(value): key
            for key, value in self.public_id2label.items()
        }
        self.sequence_id2label = dict(translation.sequence_id2label)
        self.public_to_sequence = dict(translation.public_to_sequence)
        self.sequence_to_public = dict(translation.sequence_to_public)
        self.label_translation_metadata = (
            translation.metadata()
            if label_translation_metadata is None
            else validate_label_translation_metadata(
                label_translation_metadata,
                translation,
            )
        )
        self.label_translation_sha256 = translation.sha256
        self.internal_id2label = {idx: f"label_{idx}" for idx in self.sequence_id2label}
        self.serializer = T5LayoutSequence(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        self.gen_t_serializer = T5LayoutSequenceForGenT(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        self.gen_r_serializer = T5LayoutSequenceForGenR(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        super().__init__(tokenizer=tokenizer)

    @classmethod
    def from_config(
        cls,
        dataset: DatasetName | str = DEFAULT_DATASET,
        task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
        *,
        add_sep_token: bool = True,
        x_grid: int = 128,
        y_grid: int = 128,
        id2label: dict[int, str] | None = None,
    ) -> "LayoutFormerPPProcessor":
        """Construct processor and tokenizer without external files."""
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        labels = labels_for_dataset(normalized_dataset)
        tokens = build_default_tokens(labels, task=normalized_task, grid=x_grid)
        tokenizer = LayoutFormerPPTokenizer(tokens=tokens)
        return cls(
            tokenizer=tokenizer,
            dataset=normalized_dataset,
            task=normalized_task,
            add_sep_token=add_sep_token,
            x_grid=x_grid,
            y_grid=y_grid,
            id2label=id2label,
        )

    @classmethod
    def _load_tokenizer_from_pretrained(
        cls,
        sub_processor_type: str,
        pretrained_model_name_or_path: str | PathLike[str],
        subfolder: str = "",
        **kwargs: str | int | float | bool | None,
    ) -> LayoutFormerPPTokenizer:
        """Load the local tokenizer for `ProcessorMixin.from_pretrained`."""
        _ = sub_processor_type
        path = Path(pretrained_model_name_or_path)
        tokenizer_path = path / subfolder if subfolder else path
        token = kwargs.get("token")
        return LayoutFormerPPTokenizer.from_pretrained(
            tokenizer_path,
            cache_dir=cast(str | PathLike[str] | None, kwargs.get("cache_dir")),
            force_download=bool(kwargs.get("force_download", False)),
            local_files_only=bool(kwargs.get("local_files_only", False)),
            token=token if isinstance(token, str | bool) else None,
            revision=str(kwargs.get("revision", "main")),
        )

    def normalize_condition_type(
        self, condition_type: ConditionType | str
    ) -> SupportedConditionType:
        """Normalize public condition aliases."""
        try:
            condition = normalize_common_condition_type(condition_type)
        except ValueError as exc:
            raise ValueError(f"Unsupported condition_type: {condition_type}") from exc

        if condition not in SUPPORTED_CONDITIONS:
            raise ValueError(f"Unsupported condition_type: {condition_type}")

        return cast(SupportedConditionType, condition)

    def _label_to_internal_id(self, label: int | str) -> int:
        if isinstance(label, int):
            if label not in self.public_to_sequence:
                raise ValueError(f"Unknown label: {label}")

            return self.public_to_sequence[label]
        normalized = normalize_label_name(label)
        if normalized.startswith("label_"):
            raise ValueError("Raw label_<id> tokens are internal-only")

        if normalized in self.public_label2id:
            return self.public_to_sequence[self.public_label2id[normalized]]
        raise ValueError(f"Unknown label: {label}")

    def _prepare_labels(
        self,
        labels: list[list[int | str]] | Int[torch.Tensor, "batch elements"] | None,
        batch_size: int,
        mask: list[list[bool]] | None = None,
    ) -> list[list[int]]:
        if labels is None:
            return [[] for _ in range(batch_size)]
        if isinstance(labels, torch.Tensor):
            return [[int(value) for value in row] for row in labels.tolist()]
        rows: list[list[int]] = []
        for row_idx, item in enumerate(labels):
            row_mask = None if mask is None else mask[row_idx]
            rows.append(
                [
                    self._label_to_internal_id(label)
                    for idx, label in enumerate(item)
                    if row_mask is None or row_mask[idx]
                ]
            )
        return rows

    def _prepare_mask(
        self,
        mask: Bool[torch.Tensor, "batch elements"]
        | list[list[bool]]
        | list[bool]
        | None,
        *,
        batch_size: int,
        row_lengths: list[int],
    ) -> list[list[bool]] | None:
        if mask is None:
            return None
        mask_tensor = torch.as_tensor(mask, dtype=torch.bool)
        if mask_tensor.ndim == 1:
            mask_tensor = mask_tensor.unsqueeze(0)
        if mask_tensor.ndim != 2:
            raise ValueError("mask must have shape (batch, sequence)")

        if mask_tensor.size(0) != batch_size:
            raise ValueError("mask batch dimension must match labels or batch_size")

        rows = mask_tensor.tolist()
        for row, expected_length in zip(rows, row_lengths, strict=True):
            if len(row) < expected_length:
                raise ValueError("mask sequence length must cover all labels")

        return rows

    def _prepare_bbox(
        self,
        bbox: LayoutFormerPPBBoxInput,
        *,
        labels: list[list[int]],
        box_format: BoxFormat | str,
        mask: list[list[bool]] | None = None,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> list[list[list[int]]]:
        if bbox is None:
            return [[[0, 0, 1, 1] for _ in item] for item in labels]
        tensor = torch.as_tensor(bbox, dtype=torch.float32)
        if tensor.ndim == 2:
            tensor = tensor.unsqueeze(0)
        discrete_box_format = box_format
        if not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            tensor = normalize_boxes(
                tensor,
                canvas_size=canvas_size,
                box_format=box_format,
            )
            discrete_box_format = BoxFormat.xywh
        discrete = public_to_discrete_ltwh(
            tensor,
            box_format=discrete_box_format,
            x_grid=self.x_grid,
            y_grid=self.y_grid,
        )
        rows = discrete.tolist()
        if mask is None:
            return rows
        return [
            [box for idx, box in enumerate(row) if row_mask[idx]]
            for row, row_mask in zip(rows, mask, strict=True)
        ]

    def __call__(
        self,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: list[list[int | str]]
        | Int[torch.Tensor, "batch elements"]
        | None = None,
        bbox: LayoutFormerPPBBoxInput = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | list[list[bool]]
        | list[bool]
        | None = None,
        relations: list[list[tuple[int, int, int, int, int]]]
        | Int[torch.Tensor, "batch relations relation_attrs"]
        | None = None,
        batch_size: int | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Build tokenized model inputs for a public condition."""
        condition = self.normalize_condition_type(condition_type)
        batch_size = batch_size or (len(labels) if labels is not None else 1)
        row_lengths = (
            [len(row) for row in labels] if labels is not None else [0] * batch_size
        )
        prepared_mask = self._prepare_mask(
            mask,
            batch_size=batch_size,
            row_lengths=row_lengths,
        )
        internal_labels = self._prepare_labels(labels, batch_size, prepared_mask)
        internal_bbox = self._prepare_bbox(
            bbox,
            labels=internal_labels,
            box_format=box_format,
            mask=prepared_mask,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        texts: list[str] = []
        for idx in range(batch_size):
            if condition is ConditionType.unconditional:
                texts.append("")
            elif condition is ConditionType.label:
                texts.append(
                    self.gen_t_serializer.build_input_seq(
                        "gen_t", internal_labels[idx], internal_bbox[idx]
                    )
                )
            elif condition is ConditionType.label_size:
                texts.append(
                    self.gen_t_serializer.build_input_seq(
                        "gen_ts", internal_labels[idx], internal_bbox[idx]
                    )
                )
            elif condition is ConditionType.relation:
                item_relations = [] if relations is None else relations[idx]
                if isinstance(item_relations, torch.Tensor):
                    item_relations = cast(
                        list[tuple[int, int, int, int, int]],
                        [
                            tuple(int(value) for value in row)
                            for row in item_relations.tolist()
                        ],
                    )
                texts.append(
                    self.gen_r_serializer.build_input_seq(
                        internal_labels[idx], item_relations
                    )
                )
            elif condition is ConditionType.completion:
                texts.append(
                    self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
                )
            elif condition is ConditionType.refinement:
                texts.append(
                    self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
                )
            else:
                raise ValueError(f"Unsupported condition_type: {condition}")

        encoded = self.tokenizer.encode_text(texts, add_eos=True, add_bos=False)
        if return_tensors != "pt":
            raise ValueError("Only return_tensors='pt' is supported")

        return BatchEncoding(encoded)

    def post_process_layouts(
        self,
        sequences: Int[torch.Tensor, "batch tokens"],
        *,
        box_format: BoxFormat | str = BoxFormat.xywh,
        output_type: OutputType | str = OutputType.dataclass,
        return_tensors: Literal["pt"] = "pt",
    ) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:
        """Parse generated token ids to the common layout output schema."""
        texts = self.tokenizer.batch_decode(sequences, skip_special_tokens=True)
        parsed = [self.serializer.parse_seq(text.strip()) for text in texts]
        recognized: list[ParsedLayout | None] = []
        dropped_count = 0
        for item in parsed:
            if item is None:
                recognized.append(None)
                continue

            public_labels: list[int] = []
            public_boxes: list[list[int]] = []
            for label, box in zip(item.labels, item.bbox, strict=True):
                if label not in self.sequence_to_public:
                    # Original tasks/refinement.py::T5LayoutSequence.parse_seq keeps
                    # error label 0 and its parsed, possibly shifted box.
                    # tasks/ugen.py uses that parser.
                    # Drop it because the public schema has no unknown category.
                    dropped_count += 1
                    continue

                public_labels.append(self.sequence_to_public[label])
                public_boxes.append(box)

            recognized.append(ParsedLayout(labels=public_labels, bbox=public_boxes))

        if dropped_count:
            logger.warning(
                "Dropped %d unrecognized-label element%s from LayoutFormer++ output.",
                dropped_count,
                "" if dropped_count == 1 else "s",
            )

        max_len = max(
            (len(item.labels) for item in recognized if item is not None),
            default=0,
        )
        if max_len == 0:
            max_len = 1
        label_rows: list[Int[torch.Tensor, "elements"]] = []
        bbox_rows: list[Int[torch.Tensor, "elements 4"]] = []
        mask_rows: list[Bool[torch.Tensor, "elements"]] = []
        for item in recognized:
            if item is None or not item.labels:
                labels = torch.zeros(max_len, dtype=torch.long)
                boxes = torch.zeros(max_len, 4, dtype=torch.long)
                mask = torch.zeros(max_len, dtype=torch.bool)
            else:
                labels = torch.tensor(item.labels, dtype=torch.long)
                boxes = torch.tensor(item.bbox, dtype=torch.long)
                mask = torch.ones(len(labels), dtype=torch.bool)
                if len(labels) < max_len:
                    pad = max_len - len(labels)
                    labels = torch.nn.functional.pad(labels, (0, pad))
                    boxes = torch.nn.functional.pad(boxes, (0, 0, 0, pad))
                    mask = torch.nn.functional.pad(mask, (0, pad))
            label_rows.append(labels)
            bbox_rows.append(boxes)
            mask_rows.append(mask)
        bbox_ids = torch.stack(bbox_rows)
        normalized_box_format = normalize_box_format(box_format)
        bbox = discrete_ltwh_to_public(
            bbox_ids,
            box_format=normalized_box_format,
            x_grid=self.x_grid,
            y_grid=self.y_grid,
        )
        output = LayoutGenerationOutput(
            bbox=bbox.float(),
            labels=torch.stack(label_rows).long(),
            mask=torch.stack(mask_rows).bool(),
            id2label=dict(self.public_id2label),
            sequences=sequences.long(),
            intermediates={
                "generated_text": texts,
                "box_format": normalized_box_format,
            },
        )
        try:
            normalized_output_type = (
                output_type
                if isinstance(output_type, OutputType)
                else OutputType(output_type)
            )
        except ValueError as exc:
            raise ValueError(f"Unsupported output_type: {output_type}") from exc

        if normalized_output_type is OutputType.dict:
            return dict(output)
        if normalized_output_type is not OutputType.dataclass:
            assert_never(normalized_output_type)
        if return_tensors != "pt":
            raise ValueError("Only return_tensors='pt' is supported")

        return output

__init__

__init__(
    tokenizer: LayoutFormerPPTokenizer,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask
    | ConditionType
    | str = DEFAULT_TASK,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
    label_translation_metadata: LabelTranslationMetadata
    | None = None,
) -> None

Initialize serializers, label maps, and tokenizer state.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
 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
def __init__(
    self,
    tokenizer: LayoutFormerPPTokenizer,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
    label_translation_metadata: LabelTranslationMetadata | None = None,
) -> None:
    """Initialize serializers, label maps, and tokenizer state."""
    self.tokenizer = tokenizer
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
    self.task = str(normalized_task)
    self.add_sep_token = add_sep_token
    self.x_grid = x_grid
    self.y_grid = y_grid
    labels = labels_for_dataset(normalized_dataset)
    public_id2label = (
        {int(key): str(value) for key, value in id2label.items()}
        if id2label is not None
        else dict(enumerate(labels))
    )
    canonical_translation = label_translation_for_dataset(normalized_dataset)
    if normalized_dataset is DatasetName.rico25 and public_id2label != dict(
        RICO25_LABEL_TRANSLATION.public_id2label
    ):
        raise ValueError("RICO25 public id2label must match the canonical map")

    translation = build_label_translation(
        public_id2label,
        canonical_translation.sequence_id2label,
    )
    self.id2label = dict(translation.public_id2label)
    self.public_id2label = dict(translation.public_id2label)
    self.public_label2id = {
        normalize_label_name(value): key
        for key, value in self.public_id2label.items()
    }
    self.sequence_id2label = dict(translation.sequence_id2label)
    self.public_to_sequence = dict(translation.public_to_sequence)
    self.sequence_to_public = dict(translation.sequence_to_public)
    self.label_translation_metadata = (
        translation.metadata()
        if label_translation_metadata is None
        else validate_label_translation_metadata(
            label_translation_metadata,
            translation,
        )
    )
    self.label_translation_sha256 = translation.sha256
    self.internal_id2label = {idx: f"label_{idx}" for idx in self.sequence_id2label}
    self.serializer = T5LayoutSequence(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    self.gen_t_serializer = T5LayoutSequenceForGenT(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    self.gen_r_serializer = T5LayoutSequenceForGenR(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    super().__init__(tokenizer=tokenizer)

from_config classmethod

from_config(
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask
    | ConditionType
    | str = DEFAULT_TASK,
    *,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
) -> "LayoutFormerPPProcessor"

Construct processor and tokenizer without external files.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
@classmethod
def from_config(
    cls,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
    *,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
) -> "LayoutFormerPPProcessor":
    """Construct processor and tokenizer without external files."""
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    labels = labels_for_dataset(normalized_dataset)
    tokens = build_default_tokens(labels, task=normalized_task, grid=x_grid)
    tokenizer = LayoutFormerPPTokenizer(tokens=tokens)
    return cls(
        tokenizer=tokenizer,
        dataset=normalized_dataset,
        task=normalized_task,
        add_sep_token=add_sep_token,
        x_grid=x_grid,
        y_grid=y_grid,
        id2label=id2label,
    )

normalize_condition_type

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

Normalize public condition aliases.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
194
195
196
197
198
199
200
201
202
203
204
205
206
def normalize_condition_type(
    self, condition_type: ConditionType | str
) -> SupportedConditionType:
    """Normalize public condition aliases."""
    try:
        condition = normalize_common_condition_type(condition_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported condition_type: {condition_type}") from exc

    if condition not in SUPPORTED_CONDITIONS:
        raise ValueError(f"Unsupported condition_type: {condition_type}")

    return cast(SupportedConditionType, condition)

__call__

__call__(
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[Tensor, "batch elements"]
    | list[list[bool]]
    | list[bool]
    | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[Tensor, "batch relations relation_attrs"]
    | None = None,
    batch_size: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Build tokenized model inputs for a public condition.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
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
def __call__(
    self,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[torch.Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | list[list[bool]]
    | list[bool]
    | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[torch.Tensor, "batch relations relation_attrs"]
    | None = None,
    batch_size: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Build tokenized model inputs for a public condition."""
    condition = self.normalize_condition_type(condition_type)
    batch_size = batch_size or (len(labels) if labels is not None else 1)
    row_lengths = (
        [len(row) for row in labels] if labels is not None else [0] * batch_size
    )
    prepared_mask = self._prepare_mask(
        mask,
        batch_size=batch_size,
        row_lengths=row_lengths,
    )
    internal_labels = self._prepare_labels(labels, batch_size, prepared_mask)
    internal_bbox = self._prepare_bbox(
        bbox,
        labels=internal_labels,
        box_format=box_format,
        mask=prepared_mask,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    texts: list[str] = []
    for idx in range(batch_size):
        if condition is ConditionType.unconditional:
            texts.append("")
        elif condition is ConditionType.label:
            texts.append(
                self.gen_t_serializer.build_input_seq(
                    "gen_t", internal_labels[idx], internal_bbox[idx]
                )
            )
        elif condition is ConditionType.label_size:
            texts.append(
                self.gen_t_serializer.build_input_seq(
                    "gen_ts", internal_labels[idx], internal_bbox[idx]
                )
            )
        elif condition is ConditionType.relation:
            item_relations = [] if relations is None else relations[idx]
            if isinstance(item_relations, torch.Tensor):
                item_relations = cast(
                    list[tuple[int, int, int, int, int]],
                    [
                        tuple(int(value) for value in row)
                        for row in item_relations.tolist()
                    ],
                )
            texts.append(
                self.gen_r_serializer.build_input_seq(
                    internal_labels[idx], item_relations
                )
            )
        elif condition is ConditionType.completion:
            texts.append(
                self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
            )
        elif condition is ConditionType.refinement:
            texts.append(
                self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
            )
        else:
            raise ValueError(f"Unsupported condition_type: {condition}")

    encoded = self.tokenizer.encode_text(texts, add_eos=True, add_bos=False)
    if return_tensors != "pt":
        raise ValueError("Only return_tensors='pt' is supported")

    return BatchEncoding(encoded)

post_process_layouts

post_process_layouts(
    sequences: Int[Tensor, "batch tokens"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    output_type: OutputType | str = OutputType.dataclass,
    return_tensors: Literal["pt"] = "pt",
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict

Parse generated token ids to the common layout output schema.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
def post_process_layouts(
    self,
    sequences: Int[torch.Tensor, "batch tokens"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    output_type: OutputType | str = OutputType.dataclass,
    return_tensors: Literal["pt"] = "pt",
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:
    """Parse generated token ids to the common layout output schema."""
    texts = self.tokenizer.batch_decode(sequences, skip_special_tokens=True)
    parsed = [self.serializer.parse_seq(text.strip()) for text in texts]
    recognized: list[ParsedLayout | None] = []
    dropped_count = 0
    for item in parsed:
        if item is None:
            recognized.append(None)
            continue

        public_labels: list[int] = []
        public_boxes: list[list[int]] = []
        for label, box in zip(item.labels, item.bbox, strict=True):
            if label not in self.sequence_to_public:
                # Original tasks/refinement.py::T5LayoutSequence.parse_seq keeps
                # error label 0 and its parsed, possibly shifted box.
                # tasks/ugen.py uses that parser.
                # Drop it because the public schema has no unknown category.
                dropped_count += 1
                continue

            public_labels.append(self.sequence_to_public[label])
            public_boxes.append(box)

        recognized.append(ParsedLayout(labels=public_labels, bbox=public_boxes))

    if dropped_count:
        logger.warning(
            "Dropped %d unrecognized-label element%s from LayoutFormer++ output.",
            dropped_count,
            "" if dropped_count == 1 else "s",
        )

    max_len = max(
        (len(item.labels) for item in recognized if item is not None),
        default=0,
    )
    if max_len == 0:
        max_len = 1
    label_rows: list[Int[torch.Tensor, "elements"]] = []
    bbox_rows: list[Int[torch.Tensor, "elements 4"]] = []
    mask_rows: list[Bool[torch.Tensor, "elements"]] = []
    for item in recognized:
        if item is None or not item.labels:
            labels = torch.zeros(max_len, dtype=torch.long)
            boxes = torch.zeros(max_len, 4, dtype=torch.long)
            mask = torch.zeros(max_len, dtype=torch.bool)
        else:
            labels = torch.tensor(item.labels, dtype=torch.long)
            boxes = torch.tensor(item.bbox, dtype=torch.long)
            mask = torch.ones(len(labels), dtype=torch.bool)
            if len(labels) < max_len:
                pad = max_len - len(labels)
                labels = torch.nn.functional.pad(labels, (0, pad))
                boxes = torch.nn.functional.pad(boxes, (0, 0, 0, pad))
                mask = torch.nn.functional.pad(mask, (0, pad))
        label_rows.append(labels)
        bbox_rows.append(boxes)
        mask_rows.append(mask)
    bbox_ids = torch.stack(bbox_rows)
    normalized_box_format = normalize_box_format(box_format)
    bbox = discrete_ltwh_to_public(
        bbox_ids,
        box_format=normalized_box_format,
        x_grid=self.x_grid,
        y_grid=self.y_grid,
    )
    output = LayoutGenerationOutput(
        bbox=bbox.float(),
        labels=torch.stack(label_rows).long(),
        mask=torch.stack(mask_rows).bool(),
        id2label=dict(self.public_id2label),
        sequences=sequences.long(),
        intermediates={
            "generated_text": texts,
            "box_format": normalized_box_format,
        },
    )
    try:
        normalized_output_type = (
            output_type
            if isinstance(output_type, OutputType)
            else OutputType(output_type)
        )
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

    if normalized_output_type is OutputType.dict:
        return dict(output)
    if normalized_output_type is not OutputType.dataclass:
        assert_never(normalized_output_type)
    if return_tensors != "pt":
        raise ValueError("Only return_tensors='pt' is supported")

    return output

LayoutFormerPPTask

Bases: StrEnum

Supported converted LayoutFormer++ checkpoint variants.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
12
13
14
15
16
17
18
19
20
class LayoutFormerPPTask(StrEnum):
    """Supported converted LayoutFormer++ checkpoint variants."""

    ugen = auto()
    gen_t = auto()
    gen_ts = auto()
    gen_r = auto()
    completion = auto()
    refinement = auto()

OutputType

Bases: StrEnum

Supported post-processing return shapes.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
23
24
25
26
27
class OutputType(StrEnum):
    """Supported post-processing return shapes."""

    dataclass = auto()
    dict = auto()

LayoutFormerPPTokenizer

Bases: WhitespaceTokenizerMixin, PreTrainedTokenizer

Whitespace tokenizer backed by the released vocab.json format.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class LayoutFormerPPTokenizer(WhitespaceTokenizerMixin, PreTrainedTokenizer):
    """Whitespace tokenizer backed by the released `vocab.json` format."""

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

    def __init__(
        self,
        vocab_file: str | None = None,
        tokens: list[str] | None = None,
        x_grid: int = 128,
        y_grid: int = 128,
        bbox_order: BoxFormat | str = BoxFormat.ltwh,
        bos_token: str = "<bos>",
        eos_token: str = "<eos>",
        pad_token: str = "<pad>",
        sep_token: str = "<sep>",
        unk_token: str = "<unk>",
        model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
        padding_side: str = "right",
        truncation_side: str = "right",
        clean_up_tokenization_spaces: bool = False,
        added_tokens_decoder: dict[int | str, str] | None = None,
        backend: str = "custom",
        tokenizer_file: str | None = None,
        name_or_path: str = "",
        is_local: bool = False,
        local_files_only: bool = False,
        processor_class: str | None = None,
    ) -> None:
        """Initialize a tokenizer from a vocab file or synthetic token list."""
        _ = (backend, tokenizer_file, is_local, local_files_only, processor_class)
        self.x_grid = x_grid
        self.y_grid = y_grid
        self.bbox_order = str(normalize_box_format(bbox_order))
        self._token2id, self._id2token = build_token_maps(
            vocab_file=vocab_file,
            tokens=tokens,
            base_tokens=("<bos>", "<eos>", "<pad>", "<sep>", "<unk>"),
        )
        tokenizer_kwargs: dict[str, object] = {
            "bos_token": bos_token,
            "eos_token": eos_token,
            "pad_token": pad_token,
            "sep_token": sep_token,
            "unk_token": unk_token,
            "model_max_length": model_max_length,
            "padding_side": padding_side,
            "truncation_side": truncation_side,
            "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
            "backend": backend,
            "name_or_path": name_or_path,
        }
        if added_tokens_decoder is not None:
            tokenizer_kwargs["added_tokens_decoder"] = added_tokens_decoder
        super().__init__(**tokenizer_kwargs)

    def save_vocabulary(
        self, save_directory: str, filename_prefix: str | None = None
    ) -> tuple[str]:
        """Save `vocab.json` in checkpoint-compatible token-to-id format."""
        return save_json_vocabulary(
            save_directory=save_directory,
            filename="vocab.json",
            data=self._token2id,
            filename_prefix=filename_prefix,
        )

    def save_pretrained(
        self,
        save_directory: str | PathLike[str],
        legacy_format: bool | None = None,
        filename_prefix: str | None = None,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> tuple[str, ...]:
        """Save tokenizer files plus LayoutFormer++ tokenizer metadata."""
        _ = kwargs
        paths = super().save_pretrained(
            str(save_directory),
            legacy_format=legacy_format,
            filename_prefix=filename_prefix,
            push_to_hub=push_to_hub,
        )
        metadata = {
            "x_grid": self.x_grid,
            "y_grid": self.y_grid,
            "bbox_order": self.bbox_order,
        }
        with (Path(save_directory) / "layoutformerpp_tokenizer_config.json").open(
            "w"
        ) as f:
            json.dump(metadata, f, indent=2, sort_keys=True)
        return paths

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        x_grid: int | None = None,
        y_grid: int | None = None,
        bbox_order: BoxFormat | str | None = None,
    ) -> "LayoutFormerPPTokenizer":
        """Load tokenizer and LayoutFormer++ metadata."""
        path = Path(pretrained_model_name_or_path)
        metadata_path = path / "layoutformerpp_tokenizer_config.json"
        metadata: dict[str, object] = {}
        if metadata_path.exists():
            with metadata_path.open() as f:
                metadata = json.load(f)
        if x_grid is not None:
            metadata["x_grid"] = x_grid
        if y_grid is not None:
            metadata["y_grid"] = y_grid
        if bbox_order is not None:
            metadata["bbox_order"] = bbox_order
        return cast(
            "LayoutFormerPPTokenizer",
            super().from_pretrained(
                str(pretrained_model_name_or_path),
                cache_dir=cache_dir,
                force_download=force_download,
                local_files_only=local_files_only,
                token=token,
                revision=revision,
                **metadata,
            ),
        )

    def encode_text(
        self, text: str | list[str], *, add_eos: bool = True, add_bos: bool = False
    ) -> BatchEncoding:
        """Tokenize reference-style text while matching the original EOS/BOS behavior."""
        if isinstance(text, str):
            texts = [text]
        else:
            texts = text
        normalized = []
        for item in texts:
            tokens = item.strip().split()
            if add_eos:
                tokens.append(self.eos_token)
            if add_bos:
                tokens.insert(0, self.bos_token)
            normalized.append(" ".join(tokens))
        return self(
            normalized, padding=True, add_special_tokens=False, return_tensors="pt"
        )

__init__

__init__(
    vocab_file: str | None = None,
    tokens: list[str] | None = None,
    x_grid: int = 128,
    y_grid: int = 128,
    bbox_order: BoxFormat | str = BoxFormat.ltwh,
    bos_token: str = "<bos>",
    eos_token: str = "<eos>",
    pad_token: str = "<pad>",
    sep_token: str = "<sep>",
    unk_token: str = "<unk>",
    model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
    padding_side: str = "right",
    truncation_side: str = "right",
    clean_up_tokenization_spaces: bool = False,
    added_tokens_decoder: dict[int | str, str]
    | None = None,
    backend: str = "custom",
    tokenizer_file: str | None = None,
    name_or_path: str = "",
    is_local: bool = False,
    local_files_only: bool = False,
    processor_class: str | None = None,
) -> None

Initialize a tokenizer from a vocab file or synthetic token list.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def __init__(
    self,
    vocab_file: str | None = None,
    tokens: list[str] | None = None,
    x_grid: int = 128,
    y_grid: int = 128,
    bbox_order: BoxFormat | str = BoxFormat.ltwh,
    bos_token: str = "<bos>",
    eos_token: str = "<eos>",
    pad_token: str = "<pad>",
    sep_token: str = "<sep>",
    unk_token: str = "<unk>",
    model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
    padding_side: str = "right",
    truncation_side: str = "right",
    clean_up_tokenization_spaces: bool = False,
    added_tokens_decoder: dict[int | str, str] | None = None,
    backend: str = "custom",
    tokenizer_file: str | None = None,
    name_or_path: str = "",
    is_local: bool = False,
    local_files_only: bool = False,
    processor_class: str | None = None,
) -> None:
    """Initialize a tokenizer from a vocab file or synthetic token list."""
    _ = (backend, tokenizer_file, is_local, local_files_only, processor_class)
    self.x_grid = x_grid
    self.y_grid = y_grid
    self.bbox_order = str(normalize_box_format(bbox_order))
    self._token2id, self._id2token = build_token_maps(
        vocab_file=vocab_file,
        tokens=tokens,
        base_tokens=("<bos>", "<eos>", "<pad>", "<sep>", "<unk>"),
    )
    tokenizer_kwargs: dict[str, object] = {
        "bos_token": bos_token,
        "eos_token": eos_token,
        "pad_token": pad_token,
        "sep_token": sep_token,
        "unk_token": unk_token,
        "model_max_length": model_max_length,
        "padding_side": padding_side,
        "truncation_side": truncation_side,
        "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
        "backend": backend,
        "name_or_path": name_or_path,
    }
    if added_tokens_decoder is not None:
        tokenizer_kwargs["added_tokens_decoder"] = added_tokens_decoder
    super().__init__(**tokenizer_kwargs)

save_vocabulary

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

Save vocab.json in checkpoint-compatible token-to-id format.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
78
79
80
81
82
83
84
85
86
87
def save_vocabulary(
    self, save_directory: str, filename_prefix: str | None = None
) -> tuple[str]:
    """Save `vocab.json` in checkpoint-compatible token-to-id format."""
    return save_json_vocabulary(
        save_directory=save_directory,
        filename="vocab.json",
        data=self._token2id,
        filename_prefix=filename_prefix,
    )

save_pretrained

save_pretrained(
    save_directory: str | PathLike[str],
    legacy_format: bool | None = None,
    filename_prefix: str | None = None,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> tuple[str, ...]

Save tokenizer files plus LayoutFormer++ tokenizer metadata.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
 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
def save_pretrained(
    self,
    save_directory: str | PathLike[str],
    legacy_format: bool | None = None,
    filename_prefix: str | None = None,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> tuple[str, ...]:
    """Save tokenizer files plus LayoutFormer++ tokenizer metadata."""
    _ = kwargs
    paths = super().save_pretrained(
        str(save_directory),
        legacy_format=legacy_format,
        filename_prefix=filename_prefix,
        push_to_hub=push_to_hub,
    )
    metadata = {
        "x_grid": self.x_grid,
        "y_grid": self.y_grid,
        "bbox_order": self.bbox_order,
    }
    with (Path(save_directory) / "layoutformerpp_tokenizer_config.json").open(
        "w"
    ) as f:
        json.dump(metadata, f, indent=2, sort_keys=True)
    return paths

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    x_grid: int | None = None,
    y_grid: int | None = None,
    bbox_order: BoxFormat | str | None = None,
) -> "LayoutFormerPPTokenizer"

Load tokenizer and LayoutFormer++ metadata.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    x_grid: int | None = None,
    y_grid: int | None = None,
    bbox_order: BoxFormat | str | None = None,
) -> "LayoutFormerPPTokenizer":
    """Load tokenizer and LayoutFormer++ metadata."""
    path = Path(pretrained_model_name_or_path)
    metadata_path = path / "layoutformerpp_tokenizer_config.json"
    metadata: dict[str, object] = {}
    if metadata_path.exists():
        with metadata_path.open() as f:
            metadata = json.load(f)
    if x_grid is not None:
        metadata["x_grid"] = x_grid
    if y_grid is not None:
        metadata["y_grid"] = y_grid
    if bbox_order is not None:
        metadata["bbox_order"] = bbox_order
    return cast(
        "LayoutFormerPPTokenizer",
        super().from_pretrained(
            str(pretrained_model_name_or_path),
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **metadata,
        ),
    )

encode_text

encode_text(
    text: str | list[str],
    *,
    add_eos: bool = True,
    add_bos: bool = False,
) -> BatchEncoding

Tokenize reference-style text while matching the original EOS/BOS behavior.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def encode_text(
    self, text: str | list[str], *, add_eos: bool = True, add_bos: bool = False
) -> BatchEncoding:
    """Tokenize reference-style text while matching the original EOS/BOS behavior."""
    if isinstance(text, str):
        texts = [text]
    else:
        texts = text
    normalized = []
    for item in texts:
        tokens = item.strip().split()
        if add_eos:
            tokens.append(self.eos_token)
        if add_bos:
            tokens.insert(0, self.bos_token)
        normalized.append(" ".join(tokens))
    return self(
        normalized, padding=True, add_special_tokens=False, return_tensors="pt"
    )

configuration_layoutformerpp

Configuration for LayoutFormer++.

TaskDefaults

Bases: TypedDict

Evaluation defaults for one dataset/condition pair.

Attributes:

Name Type Description
max_position_embeddings int

Upper bound for the model's input token sequence.

decode_max_length int

Evaluation-time budget for generated tokens.

eval_seed int

Evaluation-time seed used by stochastic decoding.

Values are selected per dataset and condition to reproduce the evaluation recipes documented in models/layoutformerpp/REPRODUCING.md.

Source code in models/layoutformerpp/src/layoutformerpp/configuration_layoutformerpp.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class TaskDefaults(TypedDict, total=False):
    """Evaluation defaults for one dataset/condition pair.

    Attributes:
        max_position_embeddings: Upper bound for the model's input token
            sequence.
        decode_max_length: Evaluation-time budget for generated tokens.
        eval_seed: Evaluation-time seed used by stochastic decoding.

    Values are selected per dataset and condition to reproduce the evaluation
    recipes documented in ``models/layoutformerpp/REPRODUCING.md``.
    """

    max_position_embeddings: int
    decode_max_length: int
    eval_seed: int

LayoutFormerPPConfig

Bases: PretrainedConfig

Stores model, tokenizer, and task defaults for converted checkpoints.

Source code in models/layoutformerpp/src/layoutformerpp/configuration_layoutformerpp.py
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
class LayoutFormerPPConfig(PretrainedConfig):
    """Stores model, tokenizer, and task defaults for converted checkpoints."""

    model_type = "layoutformerpp"

    def __init__(
        self,
        vocab_size: int = 0,
        max_position_embeddings: int | None = None,
        d_model: int = 512,
        encoder_layers: int = 8,
        decoder_layers: int | None = None,
        encoder_attention_heads: int = 8,
        decoder_attention_heads: int | None = None,
        dropout: float = 0.1,
        dim_feedforward: int | None = None,
        share_embedding: bool = True,
        dataset: DatasetName | str = DatasetName.rico25,
        task: LayoutFormerPPTask | ConditionType | str = LayoutFormerPPTask.gen_t,
        max_num_elements: int = 20,
        bbox_format: BoxFormat | str = BoxFormat.ltwh,
        default_box_format: BoxFormat | str = BoxFormat.xywh,
        discrete_x_grid: int = 128,
        discrete_y_grid: int = 128,
        add_sep_token: bool = True,
        sort_by_dict: bool = True,
        add_task_embedding: bool = False,
        add_task_prompt_token_in_model: bool = False,
        num_task_prompt_token: int = 1,
        task_id: int | None = None,
        decode_max_length: int | None = None,
        eval_seed: int | None = None,
        gen_t_add_unk_token: bool = False,
        gen_ts_add_unk_token: bool = False,
        gen_r_add_unk_token: bool = False,
        gen_r_compact: bool = False,
        bos_token_id: int = 0,
        eos_token_id: int = 1,
        pad_token_id: int = 2,
        is_encoder_decoder: bool = True,
        condition_type: ConditionType | str | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        architectures: list[str] | None = None,
        output_hidden_states: bool | None = False,
        output_attentions: bool | None = False,
        return_dict: bool | None = True,
        chunk_size_feed_forward: int = 0,
        problem_type: Literal[
            "regression", "single_label_classification", "multi_label_classification"
        ]
        | None = None,
        id2label: dict[int | str, str] | None = None,
        label2id: dict[str, int] | None = None,
        label_translation_metadata: LabelTranslationMetadata | None = None,
        torch_dtype: str | None = None,
        dtype: str | None = None,
        tie_word_embeddings: bool = True,
        task_specific_params: dict[
            str, str | int | float | bool | list[str | int | float | bool]
        ]
        | None = None,
        name_or_path: str = "",
        _name_or_path: str | None = None,
        _commit_hash: str | None = None,
        attn_implementation: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize architecture and task-specific generation defaults."""
        _ = (condition_type, model_type, transformers_version)
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        translation = label_translation_for_dataset(normalized_dataset)
        canonical_id2label = dict(translation.public_id2label)
        if (
            id2label is not None
            and {int(key): str(value) for key, value in id2label.items()}
            != canonical_id2label
        ):
            raise ValueError("id2label does not match the canonical dataset map")

        normalized_id2label: dict[int, str] = {
            key: value for key, value in canonical_id2label.items()
        }
        label2id = {value: key for key, value in canonical_id2label.items()}
        self.label_translation_metadata = (
            translation.metadata()
            if label_translation_metadata is None
            else validate_label_translation_metadata(
                label_translation_metadata,
                translation,
            )
        )
        condition_type = TASK_TO_CONDITION[normalized_task]
        self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
        self.task = str(normalized_task)
        self.condition_type = str(condition_type)
        defaults = TASK_DEFAULTS.get((normalized_dataset, condition_type), {})
        if max_position_embeddings is None:
            max_position_embeddings = int(defaults.get("max_position_embeddings", 150))
        if decode_max_length is None:
            decode_max_length = int(defaults.get("decode_max_length", 120))
        if eval_seed is None:
            eval_seed = int(defaults.get("eval_seed", 100))

        self.vocab_size = vocab_size
        self.max_position_embeddings = max_position_embeddings
        self.d_model = d_model
        self.encoder_layers = encoder_layers
        self.decoder_layers = (
            decoder_layers if decoder_layers is not None else encoder_layers
        )
        self.encoder_attention_heads = encoder_attention_heads
        self.decoder_attention_heads = (
            decoder_attention_heads
            if decoder_attention_heads is not None
            else encoder_attention_heads
        )
        self.dropout = dropout
        self.dim_feedforward = (
            dim_feedforward if dim_feedforward is not None else d_model * 4
        )
        self.share_embedding = share_embedding
        self.max_num_elements = max_num_elements
        self.bbox_format = str(normalize_box_format(bbox_format))
        self.default_box_format = str(normalize_box_format(default_box_format))

        self.discrete_x_grid = discrete_x_grid
        self.discrete_y_grid = discrete_y_grid
        self.add_sep_token = add_sep_token
        self.sort_by_dict = sort_by_dict
        self.add_task_embedding = add_task_embedding
        self.add_task_prompt_token_in_model = add_task_prompt_token_in_model
        self.num_task_prompt_token = num_task_prompt_token
        self.task_id = task_id

        self.decode_max_length = decode_max_length
        self.eval_seed = eval_seed
        self.gen_t_add_unk_token = gen_t_add_unk_token
        self.gen_ts_add_unk_token = gen_ts_add_unk_token
        self.gen_r_add_unk_token = gen_r_add_unk_token
        self.gen_r_compact = gen_r_compact

        super().__init__(
            transformers_version=transformers_version,
            architectures=architectures,
            output_hidden_states=output_hidden_states,
            return_dict=return_dict,
            chunk_size_feed_forward=chunk_size_feed_forward,
            problem_type=problem_type,
            is_encoder_decoder=is_encoder_decoder,
            id2label=normalized_id2label,
            label2id=label2id,
            dtype=torch_dtype or dtype,
        )
        # Transformers v5 keeps model-specific token/vocabulary fields on the
        # subclass; only the common fields above belong in the base call.
        self.vocab_size = vocab_size
        self.bos_token_id = bos_token_id
        self.eos_token_id = eos_token_id
        self.pad_token_id = pad_token_id
        self.tie_word_embeddings = tie_word_embeddings
        if output_attentions is not None:
            self.output_attentions = output_attentions
        if task_specific_params is not None:
            self.task_specific_params = task_specific_params
        self.name_or_path = name_or_path if _name_or_path is None else _name_or_path
        self._commit_hash = _commit_hash
        self._attn_implementation = attn_implementation
        # Transformers v5 passes legacy and model-specific fields through the
        # tolerant config-loading path; preserve them as ordinary attributes.
        for key, value in kwargs.items():
            setattr(self, key, value)

__init__

__init__(
    vocab_size: int = 0,
    max_position_embeddings: int | None = None,
    d_model: int = 512,
    encoder_layers: int = 8,
    decoder_layers: int | None = None,
    encoder_attention_heads: int = 8,
    decoder_attention_heads: int | None = None,
    dropout: float = 0.1,
    dim_feedforward: int | None = None,
    share_embedding: bool = True,
    dataset: DatasetName | str = DatasetName.rico25,
    task: LayoutFormerPPTask
    | ConditionType
    | str = LayoutFormerPPTask.gen_t,
    max_num_elements: int = 20,
    bbox_format: BoxFormat | str = BoxFormat.ltwh,
    default_box_format: BoxFormat | str = BoxFormat.xywh,
    discrete_x_grid: int = 128,
    discrete_y_grid: int = 128,
    add_sep_token: bool = True,
    sort_by_dict: bool = True,
    add_task_embedding: bool = False,
    add_task_prompt_token_in_model: bool = False,
    num_task_prompt_token: int = 1,
    task_id: int | None = None,
    decode_max_length: int | None = None,
    eval_seed: int | None = None,
    gen_t_add_unk_token: bool = False,
    gen_ts_add_unk_token: bool = False,
    gen_r_add_unk_token: bool = False,
    gen_r_compact: bool = False,
    bos_token_id: int = 0,
    eos_token_id: int = 1,
    pad_token_id: int = 2,
    is_encoder_decoder: bool = True,
    condition_type: ConditionType | str | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    architectures: list[str] | None = None,
    output_hidden_states: bool | None = False,
    output_attentions: bool | None = False,
    return_dict: bool | None = True,
    chunk_size_feed_forward: int = 0,
    problem_type: Literal[
        "regression",
        "single_label_classification",
        "multi_label_classification",
    ]
    | None = None,
    id2label: dict[int | str, str] | None = None,
    label2id: dict[str, int] | None = None,
    label_translation_metadata: LabelTranslationMetadata
    | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    tie_word_embeddings: bool = True,
    task_specific_params: dict[
        str,
        str
        | int
        | float
        | bool
        | list[str | int | float | bool],
    ]
    | None = None,
    name_or_path: str = "",
    _name_or_path: str | None = None,
    _commit_hash: str | None = None,
    attn_implementation: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize architecture and task-specific generation defaults.

Source code in models/layoutformerpp/src/layoutformerpp/configuration_layoutformerpp.py
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
def __init__(
    self,
    vocab_size: int = 0,
    max_position_embeddings: int | None = None,
    d_model: int = 512,
    encoder_layers: int = 8,
    decoder_layers: int | None = None,
    encoder_attention_heads: int = 8,
    decoder_attention_heads: int | None = None,
    dropout: float = 0.1,
    dim_feedforward: int | None = None,
    share_embedding: bool = True,
    dataset: DatasetName | str = DatasetName.rico25,
    task: LayoutFormerPPTask | ConditionType | str = LayoutFormerPPTask.gen_t,
    max_num_elements: int = 20,
    bbox_format: BoxFormat | str = BoxFormat.ltwh,
    default_box_format: BoxFormat | str = BoxFormat.xywh,
    discrete_x_grid: int = 128,
    discrete_y_grid: int = 128,
    add_sep_token: bool = True,
    sort_by_dict: bool = True,
    add_task_embedding: bool = False,
    add_task_prompt_token_in_model: bool = False,
    num_task_prompt_token: int = 1,
    task_id: int | None = None,
    decode_max_length: int | None = None,
    eval_seed: int | None = None,
    gen_t_add_unk_token: bool = False,
    gen_ts_add_unk_token: bool = False,
    gen_r_add_unk_token: bool = False,
    gen_r_compact: bool = False,
    bos_token_id: int = 0,
    eos_token_id: int = 1,
    pad_token_id: int = 2,
    is_encoder_decoder: bool = True,
    condition_type: ConditionType | str | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    architectures: list[str] | None = None,
    output_hidden_states: bool | None = False,
    output_attentions: bool | None = False,
    return_dict: bool | None = True,
    chunk_size_feed_forward: int = 0,
    problem_type: Literal[
        "regression", "single_label_classification", "multi_label_classification"
    ]
    | None = None,
    id2label: dict[int | str, str] | None = None,
    label2id: dict[str, int] | None = None,
    label_translation_metadata: LabelTranslationMetadata | None = None,
    torch_dtype: str | None = None,
    dtype: str | None = None,
    tie_word_embeddings: bool = True,
    task_specific_params: dict[
        str, str | int | float | bool | list[str | int | float | bool]
    ]
    | None = None,
    name_or_path: str = "",
    _name_or_path: str | None = None,
    _commit_hash: str | None = None,
    attn_implementation: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize architecture and task-specific generation defaults."""
    _ = (condition_type, model_type, transformers_version)
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    translation = label_translation_for_dataset(normalized_dataset)
    canonical_id2label = dict(translation.public_id2label)
    if (
        id2label is not None
        and {int(key): str(value) for key, value in id2label.items()}
        != canonical_id2label
    ):
        raise ValueError("id2label does not match the canonical dataset map")

    normalized_id2label: dict[int, str] = {
        key: value for key, value in canonical_id2label.items()
    }
    label2id = {value: key for key, value in canonical_id2label.items()}
    self.label_translation_metadata = (
        translation.metadata()
        if label_translation_metadata is None
        else validate_label_translation_metadata(
            label_translation_metadata,
            translation,
        )
    )
    condition_type = TASK_TO_CONDITION[normalized_task]
    self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
    self.task = str(normalized_task)
    self.condition_type = str(condition_type)
    defaults = TASK_DEFAULTS.get((normalized_dataset, condition_type), {})
    if max_position_embeddings is None:
        max_position_embeddings = int(defaults.get("max_position_embeddings", 150))
    if decode_max_length is None:
        decode_max_length = int(defaults.get("decode_max_length", 120))
    if eval_seed is None:
        eval_seed = int(defaults.get("eval_seed", 100))

    self.vocab_size = vocab_size
    self.max_position_embeddings = max_position_embeddings
    self.d_model = d_model
    self.encoder_layers = encoder_layers
    self.decoder_layers = (
        decoder_layers if decoder_layers is not None else encoder_layers
    )
    self.encoder_attention_heads = encoder_attention_heads
    self.decoder_attention_heads = (
        decoder_attention_heads
        if decoder_attention_heads is not None
        else encoder_attention_heads
    )
    self.dropout = dropout
    self.dim_feedforward = (
        dim_feedforward if dim_feedforward is not None else d_model * 4
    )
    self.share_embedding = share_embedding
    self.max_num_elements = max_num_elements
    self.bbox_format = str(normalize_box_format(bbox_format))
    self.default_box_format = str(normalize_box_format(default_box_format))

    self.discrete_x_grid = discrete_x_grid
    self.discrete_y_grid = discrete_y_grid
    self.add_sep_token = add_sep_token
    self.sort_by_dict = sort_by_dict
    self.add_task_embedding = add_task_embedding
    self.add_task_prompt_token_in_model = add_task_prompt_token_in_model
    self.num_task_prompt_token = num_task_prompt_token
    self.task_id = task_id

    self.decode_max_length = decode_max_length
    self.eval_seed = eval_seed
    self.gen_t_add_unk_token = gen_t_add_unk_token
    self.gen_ts_add_unk_token = gen_ts_add_unk_token
    self.gen_r_add_unk_token = gen_r_add_unk_token
    self.gen_r_compact = gen_r_compact

    super().__init__(
        transformers_version=transformers_version,
        architectures=architectures,
        output_hidden_states=output_hidden_states,
        return_dict=return_dict,
        chunk_size_feed_forward=chunk_size_feed_forward,
        problem_type=problem_type,
        is_encoder_decoder=is_encoder_decoder,
        id2label=normalized_id2label,
        label2id=label2id,
        dtype=torch_dtype or dtype,
    )
    # Transformers v5 keeps model-specific token/vocabulary fields on the
    # subclass; only the common fields above belong in the base call.
    self.vocab_size = vocab_size
    self.bos_token_id = bos_token_id
    self.eos_token_id = eos_token_id
    self.pad_token_id = pad_token_id
    self.tie_word_embeddings = tie_word_embeddings
    if output_attentions is not None:
        self.output_attentions = output_attentions
    if task_specific_params is not None:
        self.task_specific_params = task_specific_params
    self.name_or_path = name_or_path if _name_or_path is None else _name_or_path
    self._commit_hash = _commit_hash
    self._attn_implementation = attn_implementation
    # Transformers v5 passes legacy and model-specific fields through the
    # tolerant config-loading path; preserve them as ordinary attributes.
    for key, value in kwargs.items():
        setattr(self, key, value)

constraints

Token constraints used by LayoutFormer++ autoregressive generation.

LayoutFormerPPGenerationConstraint dataclass

Mask output tokens using the public task conditions.

Source code in models/layoutformerpp/src/layoutformerpp/constraints.py
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
@dataclass
class LayoutFormerPPGenerationConstraint:
    """Mask output tokens using the public task conditions."""

    tokenizer: LayoutFormerPPTokenizer
    condition: ConditionType
    labels: list[list[int]]
    bboxes: list[list[list[int]]]
    relations: (
        Int[torch.Tensor, "batch relations fields"]
        | Int[torch.Tensor, "relations fields"]
        | RelationRows
        | None
    ) = None
    x_grid: int = 128
    y_grid: int = 128
    add_sep_token: bool = True
    special_token_ids: set[int] = field(init=False)
    number_token_ids: set[int] = field(init=False)
    label_token_ids: set[int] = field(init=False)
    _label_tokens: set[str] = field(init=False)
    label_constraints: list[list[int]] = field(init=False)
    size_constraints: list[list[list[int]]] = field(init=False)
    relation_constraints: list[list[list[tuple[str, int | None]]]] = field(init=False)
    decode_state: list[list[_DecodeState]] = field(init=False)
    _number_tokens: dict[str, int] = field(init=False)

    def __post_init__(self) -> None:
        """Prepare token ids and independent decoding states for each row."""
        number_tokens = [str(value) for value in range(max(self.x_grid, self.y_grid))]
        number_ids = _token_ids(self.tokenizer, number_tokens)
        self._number_tokens = dict(zip(number_tokens, number_ids, strict=True))
        self.number_token_ids = set(number_ids)
        self.special_token_ids = {
            self.tokenizer.pad_token_id,
            self.tokenizer.eos_token_id,
        }
        label_tokens = [f"label_{label}" for row in self.labels for label in row]
        self._label_tokens = set(label_tokens)
        self.label_token_ids = set(_token_ids(self.tokenizer, label_tokens))
        self.label_constraints = [
            _token_ids(self.tokenizer, [f"label_{label}" for label in row])
            for row in self.labels
        ]
        self.size_constraints = [
            [
                _token_ids(self.tokenizer, [str(value) for value in box[2:]])
                for box in row
            ]
            for row in self.bboxes
        ]
        self.relation_constraints = self._prepare_relation_constraints()
        self.decode_state = [[_DecodeState(len(row))] for row in self.labels]

    def _prepare_relation_constraints(
        self,
    ) -> list[list[list[tuple[str, int | None]]]]:
        """Map requested relations to the constrained output elements."""
        if self.condition is not ConditionType.relation:
            return [[] for _ in self.labels]

        if self.relations is None:
            relation_rows: RelationRows = [[] for _ in self.labels]
        elif isinstance(self.relations, torch.Tensor):
            raw_relations = self.relations.tolist()
            if self.relations.ndim == 2 and len(self.labels) == 1:
                raw_relations = [raw_relations]
            elif self.relations.ndim != 3:
                raise ValueError("relations must have shape (batch, relations, 5)")

            relation_rows = [
                [tuple(int(value) for value in relation) for relation in row]
                for row in raw_relations
            ]
        else:
            relation_rows = cast(RelationRows, self.relations)

        if len(relation_rows) != len(self.labels):
            raise ValueError("relation batch dimension must match labels")

        type_names = [str(relation_type) for relation_type in RELATION_TYPES]
        prepared: list[list[list[tuple[str, int | None]]]] = []
        for labels, relations in zip(self.labels, relation_rows, strict=True):
            label_positions: dict[int, list[int]] = {}
            for index, label in enumerate(labels):
                label_positions.setdefault(label, []).append(index)

            item_constraints: list[list[tuple[str, int | None]]] = [[] for _ in labels]
            for label_j, index_j, label_i, index_i, relation_type_id in relations:
                if label_i == 0:
                    raise ValueError("relation target label cannot be the canvas")

                target_positions = label_positions.get(label_i)
                if target_positions is None:
                    raise ValueError(
                        f"relation target label {label_i} is absent from labels"
                    )

                if not 1 <= index_i <= len(target_positions):
                    raise ValueError(
                        f"relation target index {index_i} is out of range for label {label_i}"
                    )

                if not 0 <= relation_type_id < len(type_names):
                    raise ValueError(
                        f"relation type id {relation_type_id} is out of range"
                    )

                target_index = target_positions[index_i - 1]
                relation_type = type_names[relation_type_id]
                if label_j == 0:
                    item_constraints[target_index].append(
                        (f"canvas_{relation_type}", None)
                    )
                    continue

                source_positions = label_positions.get(label_j)
                if source_positions is None:
                    raise ValueError(
                        f"relation source label {label_j} is absent from labels"
                    )

                if not 1 <= index_j <= len(source_positions):
                    raise ValueError(
                        f"relation source index {index_j} is out of range for label {label_j}"
                    )

                source_index = source_positions[index_j - 1]
                if source_index >= target_index:
                    raise ValueError(
                        f"relation source element at index {source_index} must precede "
                        f"target element at index {target_index} in labels"
                    )

                item_constraints[target_index].append((relation_type, source_index))
                if relation_type in {"top", "bottom"}:
                    item_constraints[source_index].append(
                        (f"being_{relation_type}", None)
                    )

            prepared.append(item_constraints)

        return prepared

    @staticmethod
    def _intersect(candidates: set[int], restriction: set[int]) -> set[int]:
        """Keep the original domain when a relation imposes no values."""
        return candidates if not restriction else candidates & restriction

    def _relation_number_ids(
        self, state: _DecodeState, batch_id: int
    ) -> tuple[set[int], int | None]:
        """Build numeric candidates and a rollback point for relation decoding."""
        degree = min(self.x_grid, self.y_grid)
        candidates = set(range(degree))
        back_idx = None
        relation_constraints = self.relation_constraints[batch_id][
            state.curr_element - 1
        ]
        if not relation_constraints:
            return self.number_token_ids, None

        for relation_type, target_index in relation_constraints:
            if target_index is None:
                target_box = [0, 0, degree - 1, degree - 1]
            else:
                target_box = state.pred_bbox[target_index]
                back_idx = target_index * 5 + (
                    1
                    if state.num_bbox == 0
                    else 2
                    if state.num_bbox == 1
                    else 3
                    if state.num_bbox == 2
                    else 4
                )

            if state.num_bbox == 0:
                if relation_type == "left":
                    candidates = self._intersect(candidates, set(range(target_box[0])))
                elif relation_type == "right":
                    candidates = self._intersect(
                        candidates,
                        set(range(target_box[0] + target_box[2], degree)),
                    )
                elif relation_type == "center":
                    candidates = self._intersect(
                        candidates, set(range(target_box[0] + target_box[2]))
                    )
                elif relation_type == "canvas_equal":
                    candidates = self._intersect(candidates, {0})
            elif state.num_bbox == 1:
                if relation_type == "top":
                    candidates = self._intersect(
                        candidates, set(range(max(target_box[1] - 1, 1)))
                    )
                elif relation_type == "bottom":
                    bottom = min(target_box[1] + target_box[3] + 3, degree - 1)
                    candidates = self._intersect(candidates, set(range(bottom, degree)))
                elif relation_type in {"left", "right", "center"}:
                    candidates = self._intersect(
                        candidates, set(range(target_box[1] + target_box[3] + 1))
                    )
                elif relation_type == "canvas_equal":
                    candidates = self._intersect(candidates, {0})
                elif relation_type == "canvas_top":
                    candidates = self._intersect(
                        candidates, set(range(degree // 3 + 1))
                    )
                elif relation_type == "canvas_bottom":
                    candidates = self._intersect(
                        candidates, set(range(degree // 3, degree))
                    )
                elif relation_type == "canvas_center":
                    candidates = self._intersect(
                        candidates, set(range(2 * degree // 3))
                    )
                elif relation_type == "being_top":
                    candidates = self._intersect(candidates, set(range(5, degree)))
            elif state.num_bbox == 2:
                curr_x, curr_y = state.pred_bbox[-1]
                if relation_type == "left":
                    candidates = self._intersect(
                        candidates, set(range(max(target_box[0] - curr_x + 1, 1)))
                    )
                elif relation_type == "right":
                    candidates = self._intersect(
                        candidates, set(range(max(degree - curr_x, 1)))
                    )
                elif relation_type == "center" and target_box[0] > curr_x:
                    candidates = self._intersect(
                        candidates,
                        set(range(target_box[0] - curr_x, degree - curr_x)),
                    )
                elif relation_type == "smaller":
                    candidates = self._intersect(
                        candidates,
                        set(range(min(target_box[2] * target_box[3], degree))),
                    )
                elif relation_type == "larger":
                    min_width = max(
                        0,
                        math.floor(target_box[2] * target_box[3] * 1.1) // (degree - 1),
                    )
                    candidates = self._intersect(
                        candidates, set(range(min_width, degree))
                    )
                elif relation_type == "canvas_equal":
                    candidates = self._intersect(candidates, {degree - 1})
                elif relation_type == "equal":
                    width = min(target_box[2] * target_box[3], degree - 1)
                    candidates = self._intersect(candidates, set(range(width + 1)))
            else:
                curr_x, curr_y, curr_width = state.pred_bbox[-1]
                if relation_type in {"left", "right", "center"}:
                    if curr_y < target_box[1]:
                        candidates = self._intersect(
                            candidates,
                            set(range(target_box[1] - curr_y, degree)),
                        )
                elif relation_type == "top":
                    candidates = self._intersect(
                        candidates, set(range(max(target_box[1] - curr_y - 1, 1)))
                    )
                elif relation_type == "bottom":
                    candidates = self._intersect(
                        candidates, set(range(max(degree - curr_y, 1)))
                    )
                elif relation_type == "smaller":
                    max_height = target_box[2] * target_box[3] // max(1, curr_width)
                    candidates = self._intersect(
                        candidates,
                        set(range(max(min(max_height - 3, degree), 1))),
                    )
                elif relation_type == "larger":
                    target_area = math.floor(target_box[2] * target_box[3] * 1.1)
                    min_height = min(target_area // max(1, curr_width) + 3, degree)
                    candidates = self._intersect(
                        candidates, set(range(min_height, degree))
                    )
                elif relation_type == "equal":
                    # Preserve the float round trip that determines the reference area.
                    target_area = math.floor(
                        target_box[2]
                        * target_box[3]
                        / (self.x_grid - 1)
                        * (self.y_grid - 1)
                    )
                    height = min(
                        target_area // max(1, curr_width),
                        degree - 1,
                    )
                    candidates = self._intersect(
                        candidates, {height, max(0, height - 1), height + 1}
                    )
                elif relation_type == "canvas_equal":
                    candidates = self._intersect(candidates, {degree - 1})
                elif relation_type == "canvas_top":
                    max_height = max(1, 2 * (degree // 3 - curr_y))
                    candidates = self._intersect(candidates, set(range(max_height)))
                elif relation_type == "canvas_bottom":
                    min_height = min(2 * (2 * degree // 3 - curr_y), degree)
                    candidates = self._intersect(
                        candidates, set(range(min_height, degree))
                    )
                elif relation_type == "canvas_center":
                    first, last = degree // 3, 2 * degree // 3
                    if curr_y < first:
                        min_height = 2 * (first - curr_y)
                        max_height = 2 * (last - curr_y)
                        candidates = self._intersect(
                            candidates, set(range(min_height, max_height + 1))
                        )
                    elif curr_y < last:
                        candidates = self._intersect(
                            candidates, set(range(2 * (last - curr_y) + 1))
                        )
                elif relation_type == "being_bottom":
                    candidates = self._intersect(
                        candidates, set(range(max(degree - 1 - curr_y, 1)))
                    )

        if not candidates:
            return self.number_token_ids, back_idx

        minimum, maximum = min(candidates), max(candidates)
        candidates.update(value for value in range(minimum - 2, minimum) if value > 0)
        candidates.update(
            value for value in range(maximum + 1, maximum + 3) if value < degree
        )
        return {
            self._number_tokens[str(value)]
            for value in candidates
            if str(value) in self._number_tokens
        }, None

    def __call__(
        self,
        batch_id: int,
        step: int,
        token_ids: Int[torch.Tensor, "tokens"],
    ) -> tuple[list[int], int | None]:
        """Return allowed next-token ids and an optional relation rollback point."""
        states = self.decode_state[batch_id]
        del states[step + 1 :]
        state = copy.deepcopy(states[-1])
        back_idx = None

        if self.condition is ConditionType.relation and token_ids.numel() > 0:
            token = self.tokenizer.convert_ids_to_tokens(int(token_ids[-1]))
            if token in self._label_tokens:
                state.add_label()
            elif token in self._number_tokens:
                state.add_bbox_num(int(token))

        if state.finished:
            allowed = self.special_token_ids
        elif state.next_token_type == "element":
            allowed = {self.label_constraints[batch_id][state.curr_element]}
            state.curr_element += 1
            state.next_token_type = "number"
            state.num_bbox = 0
        elif state.next_token_type == "number":
            if self.condition is ConditionType.relation:
                allowed, back_idx = self._relation_number_ids(state, batch_id)
            elif self.condition is ConditionType.label_size and state.num_bbox >= 2:
                allowed = {
                    self.size_constraints[batch_id][state.curr_element - 1][
                        state.num_bbox - 2
                    ]
                }
            else:
                allowed = self.number_token_ids

            state.num_bbox += 1
            if state.num_bbox >= 4:
                state.next_token_type = "separator" if self.add_sep_token else "element"
        else:
            allowed = set(_token_ids(self.tokenizer, [SEP_TOKEN]))
            state.next_token_type = "element"

        states.append(state)
        return list(allowed), back_idx

__post_init__

__post_init__() -> None

Prepare token ids and independent decoding states for each row.

Source code in models/layoutformerpp/src/layoutformerpp/constraints.py
 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
def __post_init__(self) -> None:
    """Prepare token ids and independent decoding states for each row."""
    number_tokens = [str(value) for value in range(max(self.x_grid, self.y_grid))]
    number_ids = _token_ids(self.tokenizer, number_tokens)
    self._number_tokens = dict(zip(number_tokens, number_ids, strict=True))
    self.number_token_ids = set(number_ids)
    self.special_token_ids = {
        self.tokenizer.pad_token_id,
        self.tokenizer.eos_token_id,
    }
    label_tokens = [f"label_{label}" for row in self.labels for label in row]
    self._label_tokens = set(label_tokens)
    self.label_token_ids = set(_token_ids(self.tokenizer, label_tokens))
    self.label_constraints = [
        _token_ids(self.tokenizer, [f"label_{label}" for label in row])
        for row in self.labels
    ]
    self.size_constraints = [
        [
            _token_ids(self.tokenizer, [str(value) for value in box[2:]])
            for box in row
        ]
        for row in self.bboxes
    ]
    self.relation_constraints = self._prepare_relation_constraints()
    self.decode_state = [[_DecodeState(len(row))] for row in self.labels]

__call__

__call__(
    batch_id: int,
    step: int,
    token_ids: Int[Tensor, "tokens"],
) -> tuple[list[int], int | None]

Return allowed next-token ids and an optional relation rollback point.

Source code in models/layoutformerpp/src/layoutformerpp/constraints.py
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
def __call__(
    self,
    batch_id: int,
    step: int,
    token_ids: Int[torch.Tensor, "tokens"],
) -> tuple[list[int], int | None]:
    """Return allowed next-token ids and an optional relation rollback point."""
    states = self.decode_state[batch_id]
    del states[step + 1 :]
    state = copy.deepcopy(states[-1])
    back_idx = None

    if self.condition is ConditionType.relation and token_ids.numel() > 0:
        token = self.tokenizer.convert_ids_to_tokens(int(token_ids[-1]))
        if token in self._label_tokens:
            state.add_label()
        elif token in self._number_tokens:
            state.add_bbox_num(int(token))

    if state.finished:
        allowed = self.special_token_ids
    elif state.next_token_type == "element":
        allowed = {self.label_constraints[batch_id][state.curr_element]}
        state.curr_element += 1
        state.next_token_type = "number"
        state.num_bbox = 0
    elif state.next_token_type == "number":
        if self.condition is ConditionType.relation:
            allowed, back_idx = self._relation_number_ids(state, batch_id)
        elif self.condition is ConditionType.label_size and state.num_bbox >= 2:
            allowed = {
                self.size_constraints[batch_id][state.curr_element - 1][
                    state.num_bbox - 2
                ]
            }
        else:
            allowed = self.number_token_ids

        state.num_bbox += 1
        if state.num_bbox >= 4:
            state.next_token_type = "separator" if self.add_sep_token else "element"
    else:
        allowed = set(_token_ids(self.tokenizer, [SEP_TOKEN]))
        state.next_token_type = "element"

    states.append(state)
    return list(allowed), back_idx

conversion

Checkpoint conversion helpers for LayoutFormer++.

DatasetCardMetadata

Bases: TypedDict

Hub-facing metadata for one LayoutFormer++ dataset.

Source code in models/layoutformerpp/src/layoutformerpp/conversion.py
37
38
39
40
41
class DatasetCardMetadata(TypedDict):
    """Hub-facing metadata for one LayoutFormer++ dataset."""

    hub_slug: str
    dataset_id: str

layoutformerpp_hub_id

layoutformerpp_hub_id(
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
) -> str

Return the task-specific Hub id for a converted checkpoint.

Source code in models/layoutformerpp/src/layoutformerpp/conversion.py
56
57
58
59
60
61
62
63
64
65
66
def layoutformerpp_hub_id(
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
) -> str:
    """Return the task-specific Hub id for a converted checkpoint."""
    normalized_task = normalize_layoutformerpp_task(task)
    suffix = TASK_TO_CONDITION[normalized_task].replace("_", "-")
    return (
        "creative-graphic-design/layoutformerpp-"
        f"{layoutformerpp_dataset_slug(dataset)}-{suffix}"
    )

load_original_state_dict

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

Load a published LayoutFormer++ checkpoint and strip DDP prefixes.

Source code in models/layoutformerpp/src/layoutformerpp/conversion.py
69
70
71
72
73
74
75
76
def load_original_state_dict(path: Path) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load a published LayoutFormer++ checkpoint and strip DDP prefixes."""
    raw = torch.load(path, map_location="cpu")
    state = raw.get("state_dict", raw) if isinstance(raw, dict) else raw
    if not isinstance(state, dict):
        raise TypeError("checkpoint must contain a state-dict mapping")

    return {str(key).removeprefix("module."): value for key, value in state.items()}

layoutformerpp_model_card

layoutformerpp_model_card(
    *,
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
    parity_metrics: list[ParityMetricInput] | None = None,
) -> ModelCard

Build a Hub model card for one LayoutFormer++ checkpoint.

Source code in models/layoutformerpp/src/layoutformerpp/conversion.py
 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
def layoutformerpp_model_card(
    *,
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
    parity_metrics: list[ParityMetricInput] | None = None,
) -> ModelCard:
    """Build a Hub model card for one LayoutFormer++ checkpoint."""
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    model_id = layoutformerpp_hub_id(normalized_dataset, normalized_task)
    condition = TASK_TO_CONDITION[normalized_task]
    dataset_metadata = DATASET_CARD_METADATA[normalized_dataset]
    dataset_slug = dataset_metadata["hub_slug"]
    dataset_id = dataset_metadata["dataset_id"]
    metrics = parity_metrics or [
        ParityMetric(
            dataset=f"{dataset_slug}_{normalized_task}",
            tokenizer_exact="vocab.json exact",
            deterministic_exact="not run",
            logits_max_abs=0.0,
            logits_max_rel=0.0,
        )
    ]
    how_to_use = f"""
from layoutformerpp import LayoutFormerPPPipeline

pipe = LayoutFormerPPPipeline.from_pretrained("{model_id}")

out = pipe(condition_type="{condition}", labels=[["Text"]], max_length=8)
print(out.bbox, out.labels, out.mask)
"""
    return build_layout_model_card(
        model_id=model_id,
        model_name=f"LayoutFormer++ {dataset_slug} {normalized_task}",
        dataset_ids=[dataset_id],
        license="mit",
        library_name="transformers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "layoutformer++",
            "transformers",
            dataset_slug,
            str(normalized_task),
        ],
        model_details=(
            "Transformers-format conversion of the LayoutFormer++ autoregressive "
            f"layout transformer checkpoint for `{dataset_slug}` / "
            f"`{normalized_task}`. The "
            "processor returns normalized center `xywh` boxes, dataset-local "
            "labels, masks, and `id2label` using the shared `laygen.common` schema."
        ),
        intended_uses=(
            "Use this checkpoint to reproduce and evaluate LayoutFormer++ "
            f"`{dataset_slug}` / `{normalized_task}` conditional graphic layout "
            "generation in a Transformers-style API."
        ),
        limitations=(
            "This conversion preserves the released LayoutFormer++ checkpoint "
            "contract and inherits the dataset and task coverage of the original "
            "research release. Local reference parity covers tokenizer behavior, "
            "teacher-forced logits, reference greedy/top-k generation, and constrained "
            "label or label-size generation for every public `rico25` and `publaynet` "
            "LayoutFormer++ task checkpoint. This checkpoint is not intended for "
            "OCR, document understanding, or unreviewed production design decisions."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{dataset_id}` using the "
            "preprocessed LayoutFormer++ release artifacts from `jzy124/LayoutFormer`."
        ),
        parity_metrics=metrics,
        citation_bibtex=LAYOUTFORMERPP_BIBTEX,
        original_implementation_url=(
            "https://github.com/microsoft/LayoutGeneration/tree/main/LayoutFormer%2B%2B"
        ),
    )

write_layoutformerpp_model_card

write_layoutformerpp_model_card(
    output_dir: Path,
    *,
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
    parity_metrics: list[ParityMetricInput] | None = None,
) -> Path

Write the checkpoint README model card next to converted weights.

Source code in models/layoutformerpp/src/layoutformerpp/conversion.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
def write_layoutformerpp_model_card(
    output_dir: Path,
    *,
    dataset: DatasetName | str,
    task: LayoutFormerPPTask | ConditionType | str,
    parity_metrics: list[ParityMetricInput] | None = None,
) -> Path:
    """Write the checkpoint README model card next to converted weights."""
    output_dir.mkdir(parents=True, exist_ok=True)
    readme_path = output_dir / "README.md"
    readme_path.write_text(
        str(
            layoutformerpp_model_card(
                dataset=dataset,
                task=task,
                parity_metrics=parity_metrics,
            )
        ),
        encoding="utf-8",
    )
    return readme_path

geometry

Geometry helpers for LayoutFormer++ discrete ltwh boxes.

discretize_ltwh

discretize_ltwh(
    bbox: Float[Tensor, "... 4"],
    *,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Int[torch.Tensor, "... 4"]

Convert normalized ltwh values to LayoutFormer++ integer bins.

Source code in models/layoutformerpp/src/layoutformerpp/geometry.py
16
17
18
19
20
21
22
23
24
25
26
def discretize_ltwh(
    bbox: Float[torch.Tensor, "... 4"], *, x_grid: int = 128, y_grid: int = 128
) -> Int[torch.Tensor, "... 4"]:
    """Convert normalized ltwh values to LayoutFormer++ integer bins."""
    grids = (
        torch.tensor(
            (x_grid, y_grid, x_grid, y_grid), dtype=bbox.dtype, device=bbox.device
        )
        - 1
    )
    return torch.floor(bbox.clamp(0.0, 1.0) * grids).long().clamp_min(0)

continuize_ltwh

continuize_ltwh(
    ids: Int[Tensor, "... 4"],
    *,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Float[torch.Tensor, "... 4"]

Convert LayoutFormer++ integer bins back to normalized ltwh.

Source code in models/layoutformerpp/src/layoutformerpp/geometry.py
29
30
31
32
33
34
35
36
37
38
39
def continuize_ltwh(
    ids: Int[torch.Tensor, "... 4"], *, x_grid: int = 128, y_grid: int = 128
) -> Float[torch.Tensor, "... 4"]:
    """Convert LayoutFormer++ integer bins back to normalized ltwh."""
    grids = (
        torch.tensor(
            (x_grid, y_grid, x_grid, y_grid), dtype=torch.float32, device=ids.device
        )
        - 1
    )
    return ids.float().clamp_min(0) / grids

public_to_discrete_ltwh

public_to_discrete_ltwh(
    bbox: Float[Tensor, "... 4"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Int[torch.Tensor, "... 4"]

Convert public normalized boxes to internal discrete ltwh tokens.

Source code in models/layoutformerpp/src/layoutformerpp/geometry.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def public_to_discrete_ltwh(
    bbox: Float[torch.Tensor, "... 4"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Int[torch.Tensor, "... 4"]:
    """Convert public normalized boxes to internal discrete ltwh tokens."""
    fmt = normalize_box_format(box_format)
    ltwh = xywh_to_ltwh(bbox.float()) if fmt is BoxFormat.xywh else bbox.float()
    if fmt is not BoxFormat.xywh and fmt is not BoxFormat.ltwh:
        raise ValueError(f"Unsupported box_format: {box_format}")

    return discretize_ltwh(ltwh, x_grid=x_grid, y_grid=y_grid)

discrete_ltwh_to_public

discrete_ltwh_to_public(
    ids: Int[Tensor, "... 4"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Float[torch.Tensor, "... 4"]

Convert internal discrete ltwh tokens to public normalized boxes.

Source code in models/layoutformerpp/src/layoutformerpp/geometry.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def discrete_ltwh_to_public(
    ids: Int[torch.Tensor, "... 4"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    x_grid: int = 128,
    y_grid: int = 128,
) -> Float[torch.Tensor, "... 4"]:
    """Convert internal discrete ltwh tokens to public normalized boxes."""
    ltwh = continuize_ltwh(ids, x_grid=x_grid, y_grid=y_grid)
    fmt = normalize_box_format(box_format)
    if fmt is BoxFormat.xywh:
        return ltwh_to_xywh(ltwh).clamp(0.0, 1.0)
    if fmt is BoxFormat.ltwh:
        return ltwh.clamp(0.0, 1.0)
    raise ValueError(f"Unsupported box_format: {box_format}")

labels

Semantic label translation for LayoutFormer++ token sequences.

LabelTranslationMetadata

Bases: TypedDict

JSON-compatible persisted semantic label-map evidence.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
19
20
21
22
23
24
25
26
27
class LabelTranslationMetadata(TypedDict):
    """JSON-compatible persisted semantic label-map evidence."""

    normalization: str
    public_id2label: dict[int | str, str]
    sequence_id2label: dict[int | str, str]
    public_to_sequence: dict[int | str, int]
    sequence_to_public: dict[int | str, int]
    sha256: str

LabelTranslation dataclass

Persist source label maps and their name-joined translation.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
@dataclass(frozen=True, slots=True)
class LabelTranslation:
    """Persist source label maps and their name-joined translation."""

    public_id2label: Mapping[int, str]
    sequence_id2label: Mapping[int, str]
    public_to_sequence: Mapping[int, int]
    sequence_to_public: Mapping[int, int]
    canonical_json: str
    sha256: str

    def metadata(self) -> LabelTranslationMetadata:
        """Return JSON-safe maps for configs, checkpoints, and evidence."""
        public_id2label: dict[int | str, str] = {
            key: value for key, value in self.public_id2label.items()
        }
        sequence_id2label: dict[int | str, str] = {
            key: value for key, value in self.sequence_id2label.items()
        }
        public_to_sequence: dict[int | str, int] = {
            key: value for key, value in self.public_to_sequence.items()
        }
        sequence_to_public: dict[int | str, int] = {
            key: value for key, value in self.sequence_to_public.items()
        }
        return {
            "normalization": LABEL_NORMALIZATION,
            "public_id2label": public_id2label,
            "sequence_id2label": sequence_id2label,
            "public_to_sequence": public_to_sequence,
            "sequence_to_public": sequence_to_public,
            "sha256": self.sha256,
        }

metadata

metadata() -> LabelTranslationMetadata

Return JSON-safe maps for configs, checkpoints, and evidence.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
def metadata(self) -> LabelTranslationMetadata:
    """Return JSON-safe maps for configs, checkpoints, and evidence."""
    public_id2label: dict[int | str, str] = {
        key: value for key, value in self.public_id2label.items()
    }
    sequence_id2label: dict[int | str, str] = {
        key: value for key, value in self.sequence_id2label.items()
    }
    public_to_sequence: dict[int | str, int] = {
        key: value for key, value in self.public_to_sequence.items()
    }
    sequence_to_public: dict[int | str, int] = {
        key: value for key, value in self.sequence_to_public.items()
    }
    return {
        "normalization": LABEL_NORMALIZATION,
        "public_id2label": public_id2label,
        "sequence_id2label": sequence_id2label,
        "public_to_sequence": public_to_sequence,
        "sequence_to_public": sequence_to_public,
        "sha256": self.sha256,
    }

normalize_label_name

normalize_label_name(name: str) -> str

Return the exact semantic join key used by label translations.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
59
60
61
62
def normalize_label_name(name: str) -> str:
    """Return the exact semantic join key used by label translations."""
    normalized = unicodedata.normalize("NFKC", name)
    return " ".join(normalized.strip().split()).casefold()

build_label_translation

build_label_translation(
    public_id2label: Mapping[int, str],
    sequence_id2label: Mapping[int, str],
) -> LabelTranslation

Join two complete label maps by normalized semantic name.

Raises:

Type Description
ValueError

If ids, names, or inverse coverage are incomplete or ambiguous.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
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
def build_label_translation(
    public_id2label: Mapping[int, str],
    sequence_id2label: Mapping[int, str],
) -> LabelTranslation:
    """Join two complete label maps by normalized semantic name.

    Raises:
        ValueError: If ids, names, or inverse coverage are incomplete or ambiguous.
    """
    public = {int(key): str(value) for key, value in public_id2label.items()}
    sequence = {int(key): str(value) for key, value in sequence_id2label.items()}
    if set(public) != set(range(len(public))):
        raise ValueError("public ids must be contiguous from zero")

    if set(sequence) != set(range(1, len(sequence) + 1)):
        raise ValueError("sequence ids must be contiguous from one")

    if len(public) != len(sequence):
        raise ValueError("public and sequence label maps must have equal size")

    public_names = _normalized_name_to_id(public, map_name="public")
    sequence_names = _normalized_name_to_id(sequence, map_name="sequence")
    if set(public_names) != set(sequence_names):
        raise ValueError("normalized label-name sets differ")

    public_to_sequence = {
        public_id: sequence_names[normalize_label_name(label)]
        for public_id, label in public.items()
    }
    sequence_to_public = {
        sequence_id: public_id for public_id, sequence_id in public_to_sequence.items()
    }
    if len(sequence_to_public) != len(public):
        raise ValueError("label translation is not bijective")

    payload = {
        "normalization": LABEL_NORMALIZATION,
        "public_id2label": public,
        "sequence_id2label": sequence,
        "public_to_sequence": public_to_sequence,
        "sequence_to_public": sequence_to_public,
    }
    canonical_json = json.dumps(
        payload,
        ensure_ascii=False,
        separators=(",", ":"),
        sort_keys=True,
    )
    return LabelTranslation(
        public_id2label=MappingProxyType(public),
        sequence_id2label=MappingProxyType(sequence),
        public_to_sequence=MappingProxyType(public_to_sequence),
        sequence_to_public=MappingProxyType(sequence_to_public),
        canonical_json=canonical_json,
        sha256=hashlib.sha256(canonical_json.encode("utf-8")).hexdigest(),
    )

label_translation_for_dataset

label_translation_for_dataset(
    dataset: DatasetName,
) -> LabelTranslation

Return the canonical public-to-sequence label translation.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
187
188
189
190
191
192
193
194
195
def label_translation_for_dataset(dataset: DatasetName) -> LabelTranslation:
    """Return the canonical public-to-sequence label translation."""
    if dataset is DatasetName.rico25:
        return RICO25_LABEL_TRANSLATION

    if dataset is DatasetName.publaynet:
        return PUBLAYNET_LABEL_TRANSLATION

    raise ValueError(f"Unsupported LayoutFormer++ dataset: {dataset}")

validate_label_translation_metadata

validate_label_translation_metadata(
    metadata: LabelTranslationMetadata,
    expected: LabelTranslation,
) -> LabelTranslationMetadata

Normalize persisted label metadata and reject any changed field.

Source code in models/layoutformerpp/src/layoutformerpp/labels.py
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 validate_label_translation_metadata(
    metadata: LabelTranslationMetadata,
    expected: LabelTranslation,
) -> LabelTranslationMetadata:
    """Normalize persisted label metadata and reject any changed field."""
    try:
        public_raw = metadata["public_id2label"]
        sequence_raw = metadata["sequence_id2label"]
        public_to_sequence_raw = metadata["public_to_sequence"]
        sequence_to_public_raw = metadata["sequence_to_public"]
        normalization = metadata["normalization"]
        sha256 = metadata["sha256"]
    except KeyError as exc:
        raise ValueError("label translation metadata is incomplete") from exc

    public = {int(key): str(value) for key, value in public_raw.items()}
    sequence = {int(key): str(value) for key, value in sequence_raw.items()}
    rebuilt = build_label_translation(public, sequence)
    normalized = rebuilt.metadata()
    normalized["public_to_sequence"] = {
        int(key): int(value) for key, value in public_to_sequence_raw.items()
    }
    normalized["sequence_to_public"] = {
        int(key): int(value) for key, value in sequence_to_public_raw.items()
    }
    normalized["normalization"] = str(normalization)
    normalized["sha256"] = str(sha256)
    if normalized != expected.metadata() or rebuilt.sha256 != expected.sha256:
        raise ValueError("label translation metadata does not match the canonical map")

    return expected.metadata()

modeling_layoutformerpp

PyTorch model wrapper for LayoutFormer++.

PositionalEncoding

Bases: Module

Learned positional embeddings matching the original implementation.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
class PositionalEncoding(nn.Module):
    """Learned positional embeddings matching the original implementation."""

    def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 512) -> None:
        """Initialize learned position tokens."""
        super().__init__()
        self.dropout = nn.Dropout(p=dropout)
        self.pos_token = nn.Parameter(torch.rand(max_len, 1, d_model))

    def forward(
        self, x: Float[torch.Tensor, "seq batch channels"]
    ) -> Float[torch.Tensor, "seq batch channels"]:
        """Add learned position tokens to `(seq, batch, hidden)` input."""
        return self.dropout(x + self.pos_token[: x.size(0)])

__init__

__init__(
    d_model: int, dropout: float = 0.1, max_len: int = 512
) -> None

Initialize learned position tokens.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
100
101
102
103
104
def __init__(self, d_model: int, dropout: float = 0.1, max_len: int = 512) -> None:
    """Initialize learned position tokens."""
    super().__init__()
    self.dropout = nn.Dropout(p=dropout)
    self.pos_token = nn.Parameter(torch.rand(max_len, 1, d_model))

forward

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

Add learned position tokens to (seq, batch, hidden) input.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
106
107
108
109
110
def forward(
    self, x: Float[torch.Tensor, "seq batch channels"]
) -> Float[torch.Tensor, "seq batch channels"]:
    """Add learned position tokens to `(seq, batch, hidden)` input."""
    return self.dropout(x + self.pos_token[: x.size(0)])

LayoutFormerPPForConditionalGeneration

Bases: PreTrainedModel

Transformers PreTrainedModel with checkpoint-compatible module names.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
class LayoutFormerPPForConditionalGeneration(PreTrainedModel):
    """Transformers `PreTrainedModel` with checkpoint-compatible module names."""

    config_class = LayoutFormerPPConfig
    base_model_prefix = "layoutformerpp"
    main_input_name = "input_ids"
    _tied_weights_keys = {
        "dec_embedding.weight": "enc_embedding.weight",
        "out.weight": "dec_embedding.weight",
    }

    def __init__(self, config: LayoutFormerPPConfig) -> None:
        """Initialize checkpoint-compatible encoder/decoder modules."""
        super().__init__(config)
        self.d_model = config.d_model
        self.vocab_size = config.vocab_size
        self.bos_token_id = int(config.bos_token_id)
        self.pad_token_id = int(config.pad_token_id)
        self.eos_token_id = int(config.eos_token_id)

        self.enc_embedding = nn.Embedding(config.vocab_size, config.d_model)
        self.enc_pos_embedding = PositionalEncoding(
            config.d_model, config.dropout, max_len=config.max_position_embeddings
        )
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=config.d_model,
            nhead=config.encoder_attention_heads,
            dropout=config.dropout,
            dim_feedforward=config.dim_feedforward,
        )
        self.encoder = nn.TransformerEncoder(
            encoder_layer, num_layers=config.encoder_layers
        )

        self.dec_embedding = (
            self.enc_embedding
            if config.share_embedding
            else nn.Embedding(config.vocab_size, config.d_model)
        )
        self.dec_pos_embedding = PositionalEncoding(
            config.d_model, config.dropout, max_len=config.max_position_embeddings
        )
        decoder_layer = nn.TransformerDecoderLayer(
            d_model=config.d_model,
            nhead=config.decoder_attention_heads,
            dropout=config.dropout,
            dim_feedforward=config.dim_feedforward,
        )
        self.decoder = nn.TransformerDecoder(
            decoder_layer, num_layers=config.decoder_layers
        )
        self.out = nn.Linear(config.d_model, config.vocab_size, bias=False)
        self.out.weight = self.dec_embedding.weight

        self.task_embedding = None
        if config.add_task_embedding:
            self.task_embedding = nn.Embedding(6, config.d_model)

        # Preserve the reference's process-wide backtracking stream for full-split parity.
        self._backtracking_generator: torch.Generator | None = None
        self._backtracking_generator_state: bytes | None = None
        self._backtracking_random: random.Random | None = None

        self.task_prompt_embed = None
        if config.add_task_prompt_token_in_model:
            self.num_task_prompt_token = config.num_task_prompt_token
            self.task_prompt_embed = nn.Parameter(
                torch.empty(6, config.num_task_prompt_token, config.d_model)
            )
            nn.init.normal_(self.task_prompt_embed)
        self.tie_weights()
        self.all_tied_weights_keys = dict(self._tied_weights_keys)

    def encode(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        padding_mask: Bool[torch.Tensor, "batch tokens"],
        task_ids: Int[torch.Tensor, "batch"] | None = None,
    ) -> tuple[
        Float[torch.Tensor, "seq batch channels"],
        Bool[torch.Tensor, "batch seq"],
    ]:
        """Encode input token ids with optional task prompt embeddings."""
        if self.task_prompt_embed is not None:
            if task_ids is None:
                raise ValueError(
                    "task_ids are required when task prompt embeddings are enabled"
                )

            x = self.enc_embedding(input_ids)
            prompts = self.task_prompt_embed[task_ids]
            x = torch.cat([prompts, x], dim=1).permute(1, 0, 2)
            bsz = input_ids.size(0)
            prompt_mask = padding_mask.new_zeros(
                (bsz, self.num_task_prompt_token)
            ).bool()
            enc_padding_mask = torch.cat([prompt_mask, padding_mask], dim=1)
        else:
            x = self.enc_embedding(input_ids).permute(1, 0, 2)
            enc_padding_mask = padding_mask
        enc_hs = self.encoder(
            self.enc_pos_embedding(x), src_key_padding_mask=enc_padding_mask
        )
        if self.task_embedding is not None:
            if task_ids is None:
                raise ValueError(
                    "task_ids are required when task embeddings are enabled"
                )

            enc_hs = enc_hs + self.task_embedding(task_ids).unsqueeze(0)
        return enc_hs, enc_padding_mask

    def prepare_decoder_input_ids_from_labels(
        self, labels: Int[torch.Tensor, "batch tokens"]
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Shift labels right and prepend BOS."""
        bos = labels.new_full((labels.size(0), 1), self.bos_token_id)
        return torch.cat([bos, labels[:, :-1]], dim=1)

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        labels: Int[torch.Tensor, "batch tokens"] | None = None,
        decoder_input_ids: Int[torch.Tensor, "batch tokens"] | None = None,
        task_ids: Int[torch.Tensor, "batch"] | None = None,
        return_dict: bool | None = None,
    ) -> Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]:
        """Run teacher-forced LayoutFormer++ decoding."""
        if attention_mask is None:
            attention_mask = input_ids.ne(self.pad_token_id)
        padding_mask = ~attention_mask.bool()
        if decoder_input_ids is None:
            if labels is None:
                raise ValueError("decoder_input_ids or labels must be provided")

            decoder_input_ids = self.prepare_decoder_input_ids_from_labels(labels)
        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        dec_input = self.dec_pos_embedding(
            self.dec_embedding(decoder_input_ids).permute(1, 0, 2)
        )
        tgt_mask = generate_square_subsequent_mask(dec_input.size(0), dec_input.device)
        y = self.decoder(
            tgt=dec_input,
            memory=enc_hs,
            tgt_mask=tgt_mask,
            memory_key_padding_mask=enc_padding_mask,
        )
        logits = self.out(y.permute(1, 0, 2))
        loss = None
        if labels is not None:
            targets = labels.clone()
            targets[targets == self.pad_token_id] = -100
            loss = F.cross_entropy(
                logits.reshape(-1, logits.size(-1)),
                targets.reshape(-1),
                ignore_index=-100,
            )
        if return_dict is False:
            return (logits,) if loss is None else (loss, logits)
        return Seq2SeqLMOutput(loss=cast(torch.FloatTensor | None, loss), logits=logits)

    @torch.no_grad()
    def _generate_sequences(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        *,
        max_length: int | None = None,
        do_sample: bool = False,
        top_k: int = 10,
        temperature: float = 0.7,
        constrained_decoding: bool = False,
        generation_constraint_fn: Callable[
            [int, int, Int[torch.Tensor, "tokens"]], tuple[list[int], int | None]
        ]
        | None = None,
        task_ids: Int[torch.Tensor, "batch"] | None = None,
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Run the reference greedy/top-k autoregressive loop."""
        if attention_mask is None:
            attention_mask = input_ids.ne(self.pad_token_id)
        padding_mask = ~attention_mask.bool()
        max_length = max_length or self.config.decode_max_length
        if constrained_decoding:
            return self._generate_constrained_sequences(
                input_ids,
                padding_mask,
                max_length=max_length,
                top_k=top_k,
                temperature=temperature,
                generation_constraint_fn=generation_constraint_fn,
                task_ids=task_ids,
                generator=generator,
            )

        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        bsz = input_ids.size(0)
        stop = input_ids.new_zeros(bsz, dtype=torch.bool)
        pred_ids = input_ids.new_full((bsz, 1), self.bos_token_id)
        outs: list[Int[torch.Tensor, "batch"]] = []
        for idx in range(max_length):
            dec_input = self.dec_pos_embedding(
                self.dec_embedding(pred_ids).permute(1, 0, 2)
            )
            tgt_mask = generate_square_subsequent_mask(idx + 1, input_ids.device)
            y = self.decoder(
                tgt=dec_input,
                memory=enc_hs,
                tgt_mask=tgt_mask,
                memory_key_padding_mask=enc_padding_mask,
            )
            logits = self.out(y.permute(1, 0, 2)[:, -1, :])
            if generation_constraint_fn is not None:
                current = (
                    torch.stack(outs, dim=1) if outs else input_ids.new_empty((bsz, 0))
                )
                for batch_idx in range(bsz):
                    allowed, _ = generation_constraint_fn(
                        batch_idx, idx, current[batch_idx]
                    )
                    mask = torch.ones(
                        logits.size(-1), dtype=torch.bool, device=logits.device
                    )
                    mask[allowed] = False
                    logits[batch_idx].masked_fill_(mask, -math.inf)
            if do_sample:
                probs = F.softmax(top_k_logits(logits / temperature, top_k), dim=-1)
                curr = multinomial(
                    probs,
                    num_samples=1,
                    generator=generator,
                    device=probs.device,
                ).squeeze(-1)
            else:
                curr = torch.argmax(logits, dim=-1)
            eos = curr.eq(self.eos_token_id)
            curr[stop] = self.pad_token_id
            outs.append(curr)
            pred_ids = torch.cat([pred_ids, curr.unsqueeze(1)], dim=1)
            stop = torch.logical_or(stop, eos)
            if bool(torch.all(stop)):
                break
        return torch.stack(outs, dim=1)

    def _generate_constrained_sequences(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        padding_mask: Bool[torch.Tensor, "batch tokens"],
        *,
        max_length: int,
        top_k: int,
        temperature: float,
        generation_constraint_fn: Callable[
            [int, int, Int[torch.Tensor, "tokens"]], tuple[list[int], int | None]
        ]
        | None,
        task_ids: Int[torch.Tensor, "batch"] | None,
        generator: torch.Generator | None,
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Generate using a probability gate and bounded token backtracking."""
        enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
        batch_size = input_ids.size(0)
        stop = input_ids.new_zeros(batch_size, dtype=torch.bool)
        pred_ids = input_ids.new_full((batch_size, 1), self.bos_token_id)
        outs: list[Int[torch.Tensor, "batch"]] = []
        retry_steps: list[int] = []
        backtrack_rng = _backtracking_rng(
            generator,
            previous_generator=self._backtracking_generator,
            previous_state=self._backtracking_generator_state,
            previous_rng=self._backtracking_random,
        )
        backtrack_temperature = 1.5
        backtrack_flag = False
        idx = 0

        while idx < max_length:
            dec_input = self.dec_pos_embedding(
                self.dec_embedding(pred_ids).permute(1, 0, 2)
            )
            tgt_mask = generate_square_subsequent_mask(idx + 1, input_ids.device)
            y = self.decoder(
                tgt=dec_input,
                memory=enc_hs,
                tgt_mask=tgt_mask,
                memory_key_padding_mask=enc_padding_mask,
            )
            logits = self.out(y.permute(1, 0, 2)[:, -1, :])
            probabilities = F.softmax(logits, dim=-1)
            back_idx = None

            if generation_constraint_fn is not None:
                current = (
                    torch.stack(outs, dim=1)
                    if outs
                    else input_ids.new_empty((batch_size, 0))
                )
                for batch_idx in range(batch_size):
                    allowed, back_idx = generation_constraint_fn(
                        batch_idx, idx, current[batch_idx]
                    )
                    mask = torch.ones(
                        logits.size(-1), dtype=torch.bool, device=logits.device
                    )
                    mask[allowed] = False
                    logits[batch_idx].masked_fill_(mask, -math.inf)

            gated_logits, has_candidate = _probability_gate(
                logits, probabilities, threshold=0.3
            )
            randint = (
                backtrack_rng.randint if backtrack_rng is not None else random.randint
            )
            rollback = _backtracking_index(
                step=idx,
                constraint_index=back_idx,
                has_candidate=has_candidate,
                retry_count=retry_steps.count(idx),
                backtrack_pending=backtrack_flag,
                randint=randint,
            )
            if rollback is not None:
                retry_steps.append(idx)
                idx = rollback
                outs = outs[:idx]
                pred_ids = pred_ids[:, : idx + 1]
                backtrack_flag = True
                continue

            if has_candidate:
                logits[0] = gated_logits[0]

            decode_temperature = (
                backtrack_temperature if backtrack_flag else temperature
            )
            backtrack_flag = False
            probabilities = F.softmax(
                top_k_logits(logits / decode_temperature, top_k), dim=-1
            )
            curr = multinomial(
                probabilities,
                num_samples=1,
                generator=generator,
                device=probabilities.device,
            ).squeeze(-1)
            eos = curr.eq(self.eos_token_id)
            curr[stop] = self.pad_token_id
            outs.append(curr)
            pred_ids = torch.cat([pred_ids, curr.unsqueeze(1)], dim=1)
            stop = torch.logical_or(stop, eos)
            if bool(torch.all(stop)):
                break

            idx += 1

        self._backtracking_generator = generator
        self._backtracking_generator_state = (
            None if generator is None else bytes(generator.get_state().tolist())
        )
        self._backtracking_random = backtrack_rng

        return (
            torch.stack(outs, dim=1) if outs else input_ids.new_empty((batch_size, 0))
        )

__init__

__init__(config: LayoutFormerPPConfig) -> None

Initialize checkpoint-compatible encoder/decoder modules.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
def __init__(self, config: LayoutFormerPPConfig) -> None:
    """Initialize checkpoint-compatible encoder/decoder modules."""
    super().__init__(config)
    self.d_model = config.d_model
    self.vocab_size = config.vocab_size
    self.bos_token_id = int(config.bos_token_id)
    self.pad_token_id = int(config.pad_token_id)
    self.eos_token_id = int(config.eos_token_id)

    self.enc_embedding = nn.Embedding(config.vocab_size, config.d_model)
    self.enc_pos_embedding = PositionalEncoding(
        config.d_model, config.dropout, max_len=config.max_position_embeddings
    )
    encoder_layer = nn.TransformerEncoderLayer(
        d_model=config.d_model,
        nhead=config.encoder_attention_heads,
        dropout=config.dropout,
        dim_feedforward=config.dim_feedforward,
    )
    self.encoder = nn.TransformerEncoder(
        encoder_layer, num_layers=config.encoder_layers
    )

    self.dec_embedding = (
        self.enc_embedding
        if config.share_embedding
        else nn.Embedding(config.vocab_size, config.d_model)
    )
    self.dec_pos_embedding = PositionalEncoding(
        config.d_model, config.dropout, max_len=config.max_position_embeddings
    )
    decoder_layer = nn.TransformerDecoderLayer(
        d_model=config.d_model,
        nhead=config.decoder_attention_heads,
        dropout=config.dropout,
        dim_feedforward=config.dim_feedforward,
    )
    self.decoder = nn.TransformerDecoder(
        decoder_layer, num_layers=config.decoder_layers
    )
    self.out = nn.Linear(config.d_model, config.vocab_size, bias=False)
    self.out.weight = self.dec_embedding.weight

    self.task_embedding = None
    if config.add_task_embedding:
        self.task_embedding = nn.Embedding(6, config.d_model)

    # Preserve the reference's process-wide backtracking stream for full-split parity.
    self._backtracking_generator: torch.Generator | None = None
    self._backtracking_generator_state: bytes | None = None
    self._backtracking_random: random.Random | None = None

    self.task_prompt_embed = None
    if config.add_task_prompt_token_in_model:
        self.num_task_prompt_token = config.num_task_prompt_token
        self.task_prompt_embed = nn.Parameter(
            torch.empty(6, config.num_task_prompt_token, config.d_model)
        )
        nn.init.normal_(self.task_prompt_embed)
    self.tie_weights()
    self.all_tied_weights_keys = dict(self._tied_weights_keys)

encode

encode(
    input_ids: Int[Tensor, "batch tokens"],
    padding_mask: Bool[Tensor, "batch tokens"],
    task_ids: Int[Tensor, "batch"] | None = None,
) -> tuple[
    Float[torch.Tensor, "seq batch channels"],
    Bool[torch.Tensor, "batch seq"],
]

Encode input token ids with optional task prompt embeddings.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.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
def encode(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    padding_mask: Bool[torch.Tensor, "batch tokens"],
    task_ids: Int[torch.Tensor, "batch"] | None = None,
) -> tuple[
    Float[torch.Tensor, "seq batch channels"],
    Bool[torch.Tensor, "batch seq"],
]:
    """Encode input token ids with optional task prompt embeddings."""
    if self.task_prompt_embed is not None:
        if task_ids is None:
            raise ValueError(
                "task_ids are required when task prompt embeddings are enabled"
            )

        x = self.enc_embedding(input_ids)
        prompts = self.task_prompt_embed[task_ids]
        x = torch.cat([prompts, x], dim=1).permute(1, 0, 2)
        bsz = input_ids.size(0)
        prompt_mask = padding_mask.new_zeros(
            (bsz, self.num_task_prompt_token)
        ).bool()
        enc_padding_mask = torch.cat([prompt_mask, padding_mask], dim=1)
    else:
        x = self.enc_embedding(input_ids).permute(1, 0, 2)
        enc_padding_mask = padding_mask
    enc_hs = self.encoder(
        self.enc_pos_embedding(x), src_key_padding_mask=enc_padding_mask
    )
    if self.task_embedding is not None:
        if task_ids is None:
            raise ValueError(
                "task_ids are required when task embeddings are enabled"
            )

        enc_hs = enc_hs + self.task_embedding(task_ids).unsqueeze(0)
    return enc_hs, enc_padding_mask

prepare_decoder_input_ids_from_labels

prepare_decoder_input_ids_from_labels(
    labels: Int[Tensor, "batch tokens"],
) -> Int[torch.Tensor, "batch tokens"]

Shift labels right and prepend BOS.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
225
226
227
228
229
230
def prepare_decoder_input_ids_from_labels(
    self, labels: Int[torch.Tensor, "batch tokens"]
) -> Int[torch.Tensor, "batch tokens"]:
    """Shift labels right and prepend BOS."""
    bos = labels.new_full((labels.size(0), 1), self.bos_token_id)
    return torch.cat([bos, labels[:, :-1]], dim=1)

forward

forward(
    input_ids: Int[Tensor, "batch tokens"],
    attention_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    labels: Int[Tensor, "batch tokens"] | None = None,
    decoder_input_ids: Int[Tensor, "batch tokens"]
    | None = None,
    task_ids: Int[Tensor, "batch"] | None = None,
    return_dict: bool | None = None,
) -> (
    Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]
)

Run teacher-forced LayoutFormer++ decoding.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
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
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    labels: Int[torch.Tensor, "batch tokens"] | None = None,
    decoder_input_ids: Int[torch.Tensor, "batch tokens"] | None = None,
    task_ids: Int[torch.Tensor, "batch"] | None = None,
    return_dict: bool | None = None,
) -> Seq2SeqLMOutput | tuple[Float[torch.Tensor, "..."], ...]:
    """Run teacher-forced LayoutFormer++ decoding."""
    if attention_mask is None:
        attention_mask = input_ids.ne(self.pad_token_id)
    padding_mask = ~attention_mask.bool()
    if decoder_input_ids is None:
        if labels is None:
            raise ValueError("decoder_input_ids or labels must be provided")

        decoder_input_ids = self.prepare_decoder_input_ids_from_labels(labels)
    enc_hs, enc_padding_mask = self.encode(input_ids, padding_mask, task_ids)
    dec_input = self.dec_pos_embedding(
        self.dec_embedding(decoder_input_ids).permute(1, 0, 2)
    )
    tgt_mask = generate_square_subsequent_mask(dec_input.size(0), dec_input.device)
    y = self.decoder(
        tgt=dec_input,
        memory=enc_hs,
        tgt_mask=tgt_mask,
        memory_key_padding_mask=enc_padding_mask,
    )
    logits = self.out(y.permute(1, 0, 2))
    loss = None
    if labels is not None:
        targets = labels.clone()
        targets[targets == self.pad_token_id] = -100
        loss = F.cross_entropy(
            logits.reshape(-1, logits.size(-1)),
            targets.reshape(-1),
            ignore_index=-100,
        )
    if return_dict is False:
        return (logits,) if loss is None else (loss, logits)
    return Seq2SeqLMOutput(loss=cast(torch.FloatTensor | None, loss), logits=logits)

generate_square_subsequent_mask

generate_square_subsequent_mask(
    size: int, device: device
) -> Float[torch.Tensor, "target target"]

Create the causal decoder mask used by the checkpoint model.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
77
78
79
80
81
82
def generate_square_subsequent_mask(
    size: int, device: torch.device
) -> Float[torch.Tensor, "target target"]:
    """Create the causal decoder mask used by the checkpoint model."""
    mask = (torch.triu(torch.ones(size, size, device=device)) == 1).transpose(0, 1)
    return mask.float().masked_fill(mask == 0, -math.inf).masked_fill(mask == 1, 0.0)

top_k_logits

top_k_logits(
    logits: Float[Tensor, "batch vocab"], k: int
) -> Float[torch.Tensor, "batch vocab"]

Mask logits outside the top-k set.

Source code in models/layoutformerpp/src/layoutformerpp/modeling_layoutformerpp.py
85
86
87
88
89
90
91
92
93
94
def top_k_logits(
    logits: Float[torch.Tensor, "batch vocab"], k: int
) -> Float[torch.Tensor, "batch vocab"]:
    """Mask logits outside the top-k set."""
    if k <= 0 or k >= logits.size(-1):
        return logits
    values = torch.topk(logits, k).values
    out = logits.clone()
    out[out < values[:, [-1]]] = -math.inf
    return out

pipeline_layoutformerpp

Pipeline wrapper for LayoutFormer++.

LayoutFormerPPPipeline

Bases: LayoutGenerationPipeline

Compose a LayoutFormer++ model and processor for layout generation.

Parameters:

Name Type Description Default
model LayoutFormerPPForConditionalGeneration

Converted LayoutFormer++ model.

required
processor LayoutFormerPPProcessor

Matching processor/tokenizer.

required
config LayoutFormerPPConfig | None

Optional root pipeline config. Defaults to model.config.

None

Examples:

>>> processor = LayoutFormerPPProcessor.from_config(dataset="rico", task="gen_t")
>>> config = LayoutFormerPPConfig(vocab_size=processor.tokenizer.vocab_size)
>>> pipe = LayoutFormerPPPipeline(
...     model=LayoutFormerPPForConditionalGeneration(config),
...     processor=processor,
... )
>>> pipe.config.model_type
'layoutformerpp'
Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
 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
class LayoutFormerPPPipeline(LayoutGenerationPipeline):
    """Compose a LayoutFormer++ model and processor for layout generation.

    Args:
        model: Converted LayoutFormer++ model.
        processor: Matching processor/tokenizer.
        config: Optional root pipeline config. Defaults to `model.config`.

    Examples:
        >>> processor = LayoutFormerPPProcessor.from_config(dataset="rico", task="gen_t")
        >>> config = LayoutFormerPPConfig(vocab_size=processor.tokenizer.vocab_size)
        >>> pipe = LayoutFormerPPPipeline(
        ...     model=LayoutFormerPPForConditionalGeneration(config),
        ...     processor=processor,
        ... )
        >>> pipe.config.model_type
        'layoutformerpp'
    """

    config_class: ClassVar[type[PretrainedConfig]] = LayoutFormerPPConfig
    component_specs: ClassVar[dict[str, PipelineComponentSpec]] = (
        model_processor_component_specs(
            model_loader=_load_model_component,
            processor_loader=_load_processor_component,
        )
    )

    config: LayoutFormerPPConfig
    model: LayoutFormerPPForConditionalGeneration
    processor: LayoutFormerPPProcessor

    def __init__(
        self,
        model: LayoutFormerPPForConditionalGeneration,
        processor: LayoutFormerPPProcessor,
        config: LayoutFormerPPConfig | None = None,
    ) -> None:
        """Initialize the pipeline with model and processor components."""
        super().__init__(config or model.config)
        self.config = config or model.config
        self.model = model
        self.processor = processor

    @classmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> LayoutFormerPPPipeline:
        """Build a pipeline from loaded root components."""
        return cls(
            config=cast(LayoutFormerPPConfig, config),
            model=cast(LayoutFormerPPForConditionalGeneration, components["model"]),
            processor=cast(LayoutFormerPPProcessor, components["processor"]),
        )

    @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: list[list[int | str]]
        | Int[torch.Tensor, "batch elements"]
        | None = None,
        bbox: LayoutFormerPPBBoxInput = None,
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        relations: list[list[tuple[int, int, int, int, int]]]
        | Int[torch.Tensor, "batch relations relation_attrs"]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType | str = OutputType.dataclass,
        return_intermediates: bool = False,
        max_length: int | None = None,
        do_sample: bool | None = None,
        top_k: int = 10,
        temperature: float = 0.7,
    ) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:  # ty: ignore[invalid-method-override]
        """Generate layouts by encoding conditions, generating ids, and decoding.

        Args:
            batch_size: Number of layouts to generate when labels are omitted.
            seed: Convenience seed used only when `generator` is absent.
            generator: Optional PyTorch generator; takes precedence over `seed`.
            condition_type: Canonical condition type or supported alias.
            labels: Optional label conditions.
            bbox: Optional layout boxes for size/completion/refinement conditions.
            mask: Reserved public validity mask input.
            relations: Relation tuples use one-based indexes within each label. For
                non-canvas sources, the source element must precede its target in the
                label sequence used for decoding.
            num_elements: Reserved v1 interface argument.
            box_format: Input and output bounding-box format.
            normalized: Whether public boxes are normalized.
            canvas_size: Reserved v1 interface argument.
            num_inference_steps: Reserved v1 interface argument.
            output_type: Return `dataclass` or `dict`.
            return_intermediates: Reserved output detail flag.
            max_length: Optional token decode length override.
            do_sample: Optional sampling override.
            top_k: Top-k value used by the reference sampling loop.
            temperature: Sampling temperature.

        When ``max_length`` is omitted and the model config matches its dataset/task
        default, the pipeline uses 120 tokens for unconditional, completion, label,
        and label-size generation, 150 for relation generation, or input length plus
        one for refinement. This equality rule also applies when the config value was
        set explicitly; pass ``max_length`` to select another per-call limit.

        For batches larger than one, the probability gate uses row zero, relation
        rollback uses the last row's rollback point, and a rollback truncates every
        row to the same prefix.

        Returns:
            Layout generation output dataclass or dictionary.

        Raises:
            ValueError: If processor inputs or relation label/index references are invalid.
        """
        _ = (num_elements, num_inference_steps, return_intermediates)
        encoded = self.processor(
            condition_type=condition_type,
            batch_size=batch_size,
            return_tensors="pt",
            labels=labels,
            bbox=bbox,
            mask=mask,
            relations=relations,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        model_device = next(self.model.parameters()).device
        input_ids = encoded["input_ids"].to(model_device)
        attention_mask = encoded["attention_mask"].to(model_device)
        condition = self.processor.normalize_condition_type(condition_type)
        generation_constraint_fn = None
        if condition in {
            ConditionType.label,
            ConditionType.label_size,
            ConditionType.relation,
            ConditionType.refinement,
        }:
            constraint_batch_size = input_ids.size(0)
            row_lengths = (
                [len(row) for row in labels]
                if labels is not None
                else [0] * constraint_batch_size
            )
            prepared_mask = self.processor._prepare_mask(
                mask,
                batch_size=constraint_batch_size,
                row_lengths=row_lengths,
            )
            constraint_labels = self.processor._prepare_labels(
                labels,
                constraint_batch_size,
                prepared_mask,
            )
            constraint_bboxes = self.processor._prepare_bbox(
                bbox,
                labels=constraint_labels,
                box_format=box_format,
                mask=prepared_mask,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            generation_constraint_fn = LayoutFormerPPGenerationConstraint(
                tokenizer=self.processor.tokenizer,
                condition=condition,
                labels=constraint_labels,
                bboxes=constraint_bboxes,
                relations=relations,
                x_grid=self.processor.x_grid,
                y_grid=self.processor.y_grid,
                add_sep_token=self.processor.add_sep_token,
            )

        generation_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
        )
        default_do_sample = condition in {
            ConditionType.unconditional,
            ConditionType.completion,
        }
        model_task = normalize_layoutformerpp_task(self.model.config.task)
        config_default_max_length = TASK_DEFAULTS[
            (
                normalize_layoutformerpp_dataset(self.model.config.dataset),
                TASK_TO_CONDITION[model_task],
            )
        ]["decode_max_length"]
        default_max_length = self.model.config.decode_max_length
        if default_max_length == config_default_max_length:
            default_max_length = 150 if condition is ConditionType.relation else 120
            if condition is ConditionType.refinement:
                default_max_length = input_ids.size(-1) + 1

        sequences = self.model._generate_sequences(
            input_ids,
            attention_mask,
            max_length=default_max_length if max_length is None else max_length,
            do_sample=default_do_sample if do_sample is None else do_sample,
            top_k=top_k,
            temperature=temperature,
            constrained_decoding=(
                condition
                in {
                    ConditionType.label,
                    ConditionType.label_size,
                    ConditionType.relation,
                }
                and (do_sample is None or do_sample)
            ),
            generation_constraint_fn=generation_constraint_fn,
            generator=generation_generator,
        )
        return self.processor.post_process_layouts(
            sequences.cpu(),
            box_format=box_format,
            output_type=output_type,
        )

__init__

__init__(
    model: LayoutFormerPPForConditionalGeneration,
    processor: LayoutFormerPPProcessor,
    config: LayoutFormerPPConfig | None = None,
) -> None

Initialize the pipeline with model and processor components.

Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
110
111
112
113
114
115
116
117
118
119
120
def __init__(
    self,
    model: LayoutFormerPPForConditionalGeneration,
    processor: LayoutFormerPPProcessor,
    config: LayoutFormerPPConfig | None = None,
) -> None:
    """Initialize the pipeline with model and processor components."""
    super().__init__(config or model.config)
    self.config = config or model.config
    self.model = model
    self.processor = processor

__call__

__call__(
    batch_size: int = 1,
    seed: int | None = None,
    generator: Generator | None = None,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[Tensor, "batch elements"] | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[Tensor, "batch relations relation_attrs"]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    max_length: int | None = None,
    do_sample: bool | None = None,
    top_k: int = 10,
    temperature: float = 0.7,
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict

Generate layouts by encoding conditions, generating ids, and decoding.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate when labels are omitted.

1
seed int | None

Convenience seed used only when generator is absent.

None
generator Generator | None

Optional PyTorch generator; takes precedence over seed.

None
condition_type ConditionType | str

Canonical condition type or supported alias.

unconditional
labels list[list[int | str]] | Int[Tensor, 'batch elements'] | None

Optional label conditions.

None
bbox LayoutFormerPPBBoxInput

Optional layout boxes for size/completion/refinement conditions.

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

Reserved public validity mask input.

None
relations list[list[tuple[int, int, int, int, int]]] | Int[Tensor, 'batch relations relation_attrs'] | None

Relation tuples use one-based indexes within each label. For non-canvas sources, the source element must precede its target in the label sequence used for decoding.

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

Reserved v1 interface argument.

None
box_format BoxFormat | str

Input and output bounding-box format.

xywh
normalized bool

Whether public boxes are normalized.

True
canvas_size tuple[int, int] | None

Reserved v1 interface argument.

None
num_inference_steps int | None

Reserved v1 interface argument.

None
output_type OutputType | str

Return dataclass or dict.

dataclass
return_intermediates bool

Reserved output detail flag.

False
max_length int | None

Optional token decode length override.

None
do_sample bool | None

Optional sampling override.

None
top_k int

Top-k value used by the reference sampling loop.

10
temperature float

Sampling temperature.

0.7

When max_length is omitted and the model config matches its dataset/task default, the pipeline uses 120 tokens for unconditional, completion, label, and label-size generation, 150 for relation generation, or input length plus one for refinement. This equality rule also applies when the config value was set explicitly; pass max_length to select another per-call limit.

For batches larger than one, the probability gate uses row zero, relation rollback uses the last row's rollback point, and a rollback truncates every row to the same prefix.

Returns:

Type Description
LayoutGenerationOutput | LayoutFormerPPOutputDict

Layout generation output dataclass or dictionary.

Raises:

Type Description
ValueError

If processor inputs or relation label/index references are invalid.

Source code in models/layoutformerpp/src/layoutformerpp/pipeline_layoutformerpp.py
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
@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: list[list[int | str]]
    | Int[torch.Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[torch.Tensor, "batch relations relation_attrs"]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType | str = OutputType.dataclass,
    return_intermediates: bool = False,
    max_length: int | None = None,
    do_sample: bool | None = None,
    top_k: int = 10,
    temperature: float = 0.7,
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:  # ty: ignore[invalid-method-override]
    """Generate layouts by encoding conditions, generating ids, and decoding.

    Args:
        batch_size: Number of layouts to generate when labels are omitted.
        seed: Convenience seed used only when `generator` is absent.
        generator: Optional PyTorch generator; takes precedence over `seed`.
        condition_type: Canonical condition type or supported alias.
        labels: Optional label conditions.
        bbox: Optional layout boxes for size/completion/refinement conditions.
        mask: Reserved public validity mask input.
        relations: Relation tuples use one-based indexes within each label. For
            non-canvas sources, the source element must precede its target in the
            label sequence used for decoding.
        num_elements: Reserved v1 interface argument.
        box_format: Input and output bounding-box format.
        normalized: Whether public boxes are normalized.
        canvas_size: Reserved v1 interface argument.
        num_inference_steps: Reserved v1 interface argument.
        output_type: Return `dataclass` or `dict`.
        return_intermediates: Reserved output detail flag.
        max_length: Optional token decode length override.
        do_sample: Optional sampling override.
        top_k: Top-k value used by the reference sampling loop.
        temperature: Sampling temperature.

    When ``max_length`` is omitted and the model config matches its dataset/task
    default, the pipeline uses 120 tokens for unconditional, completion, label,
    and label-size generation, 150 for relation generation, or input length plus
    one for refinement. This equality rule also applies when the config value was
    set explicitly; pass ``max_length`` to select another per-call limit.

    For batches larger than one, the probability gate uses row zero, relation
    rollback uses the last row's rollback point, and a rollback truncates every
    row to the same prefix.

    Returns:
        Layout generation output dataclass or dictionary.

    Raises:
        ValueError: If processor inputs or relation label/index references are invalid.
    """
    _ = (num_elements, num_inference_steps, return_intermediates)
    encoded = self.processor(
        condition_type=condition_type,
        batch_size=batch_size,
        return_tensors="pt",
        labels=labels,
        bbox=bbox,
        mask=mask,
        relations=relations,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    model_device = next(self.model.parameters()).device
    input_ids = encoded["input_ids"].to(model_device)
    attention_mask = encoded["attention_mask"].to(model_device)
    condition = self.processor.normalize_condition_type(condition_type)
    generation_constraint_fn = None
    if condition in {
        ConditionType.label,
        ConditionType.label_size,
        ConditionType.relation,
        ConditionType.refinement,
    }:
        constraint_batch_size = input_ids.size(0)
        row_lengths = (
            [len(row) for row in labels]
            if labels is not None
            else [0] * constraint_batch_size
        )
        prepared_mask = self.processor._prepare_mask(
            mask,
            batch_size=constraint_batch_size,
            row_lengths=row_lengths,
        )
        constraint_labels = self.processor._prepare_labels(
            labels,
            constraint_batch_size,
            prepared_mask,
        )
        constraint_bboxes = self.processor._prepare_bbox(
            bbox,
            labels=constraint_labels,
            box_format=box_format,
            mask=prepared_mask,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        generation_constraint_fn = LayoutFormerPPGenerationConstraint(
            tokenizer=self.processor.tokenizer,
            condition=condition,
            labels=constraint_labels,
            bboxes=constraint_bboxes,
            relations=relations,
            x_grid=self.processor.x_grid,
            y_grid=self.processor.y_grid,
            add_sep_token=self.processor.add_sep_token,
        )

    generation_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
    )
    default_do_sample = condition in {
        ConditionType.unconditional,
        ConditionType.completion,
    }
    model_task = normalize_layoutformerpp_task(self.model.config.task)
    config_default_max_length = TASK_DEFAULTS[
        (
            normalize_layoutformerpp_dataset(self.model.config.dataset),
            TASK_TO_CONDITION[model_task],
        )
    ]["decode_max_length"]
    default_max_length = self.model.config.decode_max_length
    if default_max_length == config_default_max_length:
        default_max_length = 150 if condition is ConditionType.relation else 120
        if condition is ConditionType.refinement:
            default_max_length = input_ids.size(-1) + 1

    sequences = self.model._generate_sequences(
        input_ids,
        attention_mask,
        max_length=default_max_length if max_length is None else max_length,
        do_sample=default_do_sample if do_sample is None else do_sample,
        top_k=top_k,
        temperature=temperature,
        constrained_decoding=(
            condition
            in {
                ConditionType.label,
                ConditionType.label_size,
                ConditionType.relation,
            }
            and (do_sample is None or do_sample)
        ),
        generation_constraint_fn=generation_constraint_fn,
        generator=generation_generator,
    )
    return self.processor.post_process_layouts(
        sequences.cpu(),
        box_format=box_format,
        output_type=output_type,
    )

processing_layoutformerpp

Processor for LayoutFormer++ conditions and generated sequences.

LayoutFormerPPProcessor

Bases: ProcessorMixin

Build LayoutFormer++ text inputs and parse generated layouts.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
class LayoutFormerPPProcessor(ProcessorMixin):
    """Build LayoutFormer++ text inputs and parse generated layouts."""

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutFormerPPTokenizer"

    def __init__(
        self,
        tokenizer: LayoutFormerPPTokenizer,
        dataset: DatasetName | str = DEFAULT_DATASET,
        task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
        add_sep_token: bool = True,
        x_grid: int = 128,
        y_grid: int = 128,
        id2label: dict[int, str] | None = None,
        label_translation_metadata: LabelTranslationMetadata | None = None,
    ) -> None:
        """Initialize serializers, label maps, and tokenizer state."""
        self.tokenizer = tokenizer
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
        self.task = str(normalized_task)
        self.add_sep_token = add_sep_token
        self.x_grid = x_grid
        self.y_grid = y_grid
        labels = labels_for_dataset(normalized_dataset)
        public_id2label = (
            {int(key): str(value) for key, value in id2label.items()}
            if id2label is not None
            else dict(enumerate(labels))
        )
        canonical_translation = label_translation_for_dataset(normalized_dataset)
        if normalized_dataset is DatasetName.rico25 and public_id2label != dict(
            RICO25_LABEL_TRANSLATION.public_id2label
        ):
            raise ValueError("RICO25 public id2label must match the canonical map")

        translation = build_label_translation(
            public_id2label,
            canonical_translation.sequence_id2label,
        )
        self.id2label = dict(translation.public_id2label)
        self.public_id2label = dict(translation.public_id2label)
        self.public_label2id = {
            normalize_label_name(value): key
            for key, value in self.public_id2label.items()
        }
        self.sequence_id2label = dict(translation.sequence_id2label)
        self.public_to_sequence = dict(translation.public_to_sequence)
        self.sequence_to_public = dict(translation.sequence_to_public)
        self.label_translation_metadata = (
            translation.metadata()
            if label_translation_metadata is None
            else validate_label_translation_metadata(
                label_translation_metadata,
                translation,
            )
        )
        self.label_translation_sha256 = translation.sha256
        self.internal_id2label = {idx: f"label_{idx}" for idx in self.sequence_id2label}
        self.serializer = T5LayoutSequence(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        self.gen_t_serializer = T5LayoutSequenceForGenT(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        self.gen_r_serializer = T5LayoutSequenceForGenR(
            self.internal_id2label, add_sep_token=add_sep_token
        )
        super().__init__(tokenizer=tokenizer)

    @classmethod
    def from_config(
        cls,
        dataset: DatasetName | str = DEFAULT_DATASET,
        task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
        *,
        add_sep_token: bool = True,
        x_grid: int = 128,
        y_grid: int = 128,
        id2label: dict[int, str] | None = None,
    ) -> "LayoutFormerPPProcessor":
        """Construct processor and tokenizer without external files."""
        normalized_dataset = normalize_layoutformerpp_dataset(dataset)
        normalized_task = normalize_layoutformerpp_task(task)
        labels = labels_for_dataset(normalized_dataset)
        tokens = build_default_tokens(labels, task=normalized_task, grid=x_grid)
        tokenizer = LayoutFormerPPTokenizer(tokens=tokens)
        return cls(
            tokenizer=tokenizer,
            dataset=normalized_dataset,
            task=normalized_task,
            add_sep_token=add_sep_token,
            x_grid=x_grid,
            y_grid=y_grid,
            id2label=id2label,
        )

    @classmethod
    def _load_tokenizer_from_pretrained(
        cls,
        sub_processor_type: str,
        pretrained_model_name_or_path: str | PathLike[str],
        subfolder: str = "",
        **kwargs: str | int | float | bool | None,
    ) -> LayoutFormerPPTokenizer:
        """Load the local tokenizer for `ProcessorMixin.from_pretrained`."""
        _ = sub_processor_type
        path = Path(pretrained_model_name_or_path)
        tokenizer_path = path / subfolder if subfolder else path
        token = kwargs.get("token")
        return LayoutFormerPPTokenizer.from_pretrained(
            tokenizer_path,
            cache_dir=cast(str | PathLike[str] | None, kwargs.get("cache_dir")),
            force_download=bool(kwargs.get("force_download", False)),
            local_files_only=bool(kwargs.get("local_files_only", False)),
            token=token if isinstance(token, str | bool) else None,
            revision=str(kwargs.get("revision", "main")),
        )

    def normalize_condition_type(
        self, condition_type: ConditionType | str
    ) -> SupportedConditionType:
        """Normalize public condition aliases."""
        try:
            condition = normalize_common_condition_type(condition_type)
        except ValueError as exc:
            raise ValueError(f"Unsupported condition_type: {condition_type}") from exc

        if condition not in SUPPORTED_CONDITIONS:
            raise ValueError(f"Unsupported condition_type: {condition_type}")

        return cast(SupportedConditionType, condition)

    def _label_to_internal_id(self, label: int | str) -> int:
        if isinstance(label, int):
            if label not in self.public_to_sequence:
                raise ValueError(f"Unknown label: {label}")

            return self.public_to_sequence[label]
        normalized = normalize_label_name(label)
        if normalized.startswith("label_"):
            raise ValueError("Raw label_<id> tokens are internal-only")

        if normalized in self.public_label2id:
            return self.public_to_sequence[self.public_label2id[normalized]]
        raise ValueError(f"Unknown label: {label}")

    def _prepare_labels(
        self,
        labels: list[list[int | str]] | Int[torch.Tensor, "batch elements"] | None,
        batch_size: int,
        mask: list[list[bool]] | None = None,
    ) -> list[list[int]]:
        if labels is None:
            return [[] for _ in range(batch_size)]
        if isinstance(labels, torch.Tensor):
            return [[int(value) for value in row] for row in labels.tolist()]
        rows: list[list[int]] = []
        for row_idx, item in enumerate(labels):
            row_mask = None if mask is None else mask[row_idx]
            rows.append(
                [
                    self._label_to_internal_id(label)
                    for idx, label in enumerate(item)
                    if row_mask is None or row_mask[idx]
                ]
            )
        return rows

    def _prepare_mask(
        self,
        mask: Bool[torch.Tensor, "batch elements"]
        | list[list[bool]]
        | list[bool]
        | None,
        *,
        batch_size: int,
        row_lengths: list[int],
    ) -> list[list[bool]] | None:
        if mask is None:
            return None
        mask_tensor = torch.as_tensor(mask, dtype=torch.bool)
        if mask_tensor.ndim == 1:
            mask_tensor = mask_tensor.unsqueeze(0)
        if mask_tensor.ndim != 2:
            raise ValueError("mask must have shape (batch, sequence)")

        if mask_tensor.size(0) != batch_size:
            raise ValueError("mask batch dimension must match labels or batch_size")

        rows = mask_tensor.tolist()
        for row, expected_length in zip(rows, row_lengths, strict=True):
            if len(row) < expected_length:
                raise ValueError("mask sequence length must cover all labels")

        return rows

    def _prepare_bbox(
        self,
        bbox: LayoutFormerPPBBoxInput,
        *,
        labels: list[list[int]],
        box_format: BoxFormat | str,
        mask: list[list[bool]] | None = None,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
    ) -> list[list[list[int]]]:
        if bbox is None:
            return [[[0, 0, 1, 1] for _ in item] for item in labels]
        tensor = torch.as_tensor(bbox, dtype=torch.float32)
        if tensor.ndim == 2:
            tensor = tensor.unsqueeze(0)
        discrete_box_format = box_format
        if not normalized:
            if canvas_size is None:
                raise ValueError("canvas_size is required when normalized=False")

            tensor = normalize_boxes(
                tensor,
                canvas_size=canvas_size,
                box_format=box_format,
            )
            discrete_box_format = BoxFormat.xywh
        discrete = public_to_discrete_ltwh(
            tensor,
            box_format=discrete_box_format,
            x_grid=self.x_grid,
            y_grid=self.y_grid,
        )
        rows = discrete.tolist()
        if mask is None:
            return rows
        return [
            [box for idx, box in enumerate(row) if row_mask[idx]]
            for row, row_mask in zip(rows, mask, strict=True)
        ]

    def __call__(
        self,
        condition_type: ConditionType | str = ConditionType.unconditional,
        labels: list[list[int | str]]
        | Int[torch.Tensor, "batch elements"]
        | None = None,
        bbox: LayoutFormerPPBBoxInput = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | list[list[bool]]
        | list[bool]
        | None = None,
        relations: list[list[tuple[int, int, int, int, int]]]
        | Int[torch.Tensor, "batch relations relation_attrs"]
        | None = None,
        batch_size: int | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Build tokenized model inputs for a public condition."""
        condition = self.normalize_condition_type(condition_type)
        batch_size = batch_size or (len(labels) if labels is not None else 1)
        row_lengths = (
            [len(row) for row in labels] if labels is not None else [0] * batch_size
        )
        prepared_mask = self._prepare_mask(
            mask,
            batch_size=batch_size,
            row_lengths=row_lengths,
        )
        internal_labels = self._prepare_labels(labels, batch_size, prepared_mask)
        internal_bbox = self._prepare_bbox(
            bbox,
            labels=internal_labels,
            box_format=box_format,
            mask=prepared_mask,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        texts: list[str] = []
        for idx in range(batch_size):
            if condition is ConditionType.unconditional:
                texts.append("")
            elif condition is ConditionType.label:
                texts.append(
                    self.gen_t_serializer.build_input_seq(
                        "gen_t", internal_labels[idx], internal_bbox[idx]
                    )
                )
            elif condition is ConditionType.label_size:
                texts.append(
                    self.gen_t_serializer.build_input_seq(
                        "gen_ts", internal_labels[idx], internal_bbox[idx]
                    )
                )
            elif condition is ConditionType.relation:
                item_relations = [] if relations is None else relations[idx]
                if isinstance(item_relations, torch.Tensor):
                    item_relations = cast(
                        list[tuple[int, int, int, int, int]],
                        [
                            tuple(int(value) for value in row)
                            for row in item_relations.tolist()
                        ],
                    )
                texts.append(
                    self.gen_r_serializer.build_input_seq(
                        internal_labels[idx], item_relations
                    )
                )
            elif condition is ConditionType.completion:
                texts.append(
                    self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
                )
            elif condition is ConditionType.refinement:
                texts.append(
                    self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
                )
            else:
                raise ValueError(f"Unsupported condition_type: {condition}")

        encoded = self.tokenizer.encode_text(texts, add_eos=True, add_bos=False)
        if return_tensors != "pt":
            raise ValueError("Only return_tensors='pt' is supported")

        return BatchEncoding(encoded)

    def post_process_layouts(
        self,
        sequences: Int[torch.Tensor, "batch tokens"],
        *,
        box_format: BoxFormat | str = BoxFormat.xywh,
        output_type: OutputType | str = OutputType.dataclass,
        return_tensors: Literal["pt"] = "pt",
    ) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:
        """Parse generated token ids to the common layout output schema."""
        texts = self.tokenizer.batch_decode(sequences, skip_special_tokens=True)
        parsed = [self.serializer.parse_seq(text.strip()) for text in texts]
        recognized: list[ParsedLayout | None] = []
        dropped_count = 0
        for item in parsed:
            if item is None:
                recognized.append(None)
                continue

            public_labels: list[int] = []
            public_boxes: list[list[int]] = []
            for label, box in zip(item.labels, item.bbox, strict=True):
                if label not in self.sequence_to_public:
                    # Original tasks/refinement.py::T5LayoutSequence.parse_seq keeps
                    # error label 0 and its parsed, possibly shifted box.
                    # tasks/ugen.py uses that parser.
                    # Drop it because the public schema has no unknown category.
                    dropped_count += 1
                    continue

                public_labels.append(self.sequence_to_public[label])
                public_boxes.append(box)

            recognized.append(ParsedLayout(labels=public_labels, bbox=public_boxes))

        if dropped_count:
            logger.warning(
                "Dropped %d unrecognized-label element%s from LayoutFormer++ output.",
                dropped_count,
                "" if dropped_count == 1 else "s",
            )

        max_len = max(
            (len(item.labels) for item in recognized if item is not None),
            default=0,
        )
        if max_len == 0:
            max_len = 1
        label_rows: list[Int[torch.Tensor, "elements"]] = []
        bbox_rows: list[Int[torch.Tensor, "elements 4"]] = []
        mask_rows: list[Bool[torch.Tensor, "elements"]] = []
        for item in recognized:
            if item is None or not item.labels:
                labels = torch.zeros(max_len, dtype=torch.long)
                boxes = torch.zeros(max_len, 4, dtype=torch.long)
                mask = torch.zeros(max_len, dtype=torch.bool)
            else:
                labels = torch.tensor(item.labels, dtype=torch.long)
                boxes = torch.tensor(item.bbox, dtype=torch.long)
                mask = torch.ones(len(labels), dtype=torch.bool)
                if len(labels) < max_len:
                    pad = max_len - len(labels)
                    labels = torch.nn.functional.pad(labels, (0, pad))
                    boxes = torch.nn.functional.pad(boxes, (0, 0, 0, pad))
                    mask = torch.nn.functional.pad(mask, (0, pad))
            label_rows.append(labels)
            bbox_rows.append(boxes)
            mask_rows.append(mask)
        bbox_ids = torch.stack(bbox_rows)
        normalized_box_format = normalize_box_format(box_format)
        bbox = discrete_ltwh_to_public(
            bbox_ids,
            box_format=normalized_box_format,
            x_grid=self.x_grid,
            y_grid=self.y_grid,
        )
        output = LayoutGenerationOutput(
            bbox=bbox.float(),
            labels=torch.stack(label_rows).long(),
            mask=torch.stack(mask_rows).bool(),
            id2label=dict(self.public_id2label),
            sequences=sequences.long(),
            intermediates={
                "generated_text": texts,
                "box_format": normalized_box_format,
            },
        )
        try:
            normalized_output_type = (
                output_type
                if isinstance(output_type, OutputType)
                else OutputType(output_type)
            )
        except ValueError as exc:
            raise ValueError(f"Unsupported output_type: {output_type}") from exc

        if normalized_output_type is OutputType.dict:
            return dict(output)
        if normalized_output_type is not OutputType.dataclass:
            assert_never(normalized_output_type)
        if return_tensors != "pt":
            raise ValueError("Only return_tensors='pt' is supported")

        return output

__init__

__init__(
    tokenizer: LayoutFormerPPTokenizer,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask
    | ConditionType
    | str = DEFAULT_TASK,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
    label_translation_metadata: LabelTranslationMetadata
    | None = None,
) -> None

Initialize serializers, label maps, and tokenizer state.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
 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
def __init__(
    self,
    tokenizer: LayoutFormerPPTokenizer,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
    label_translation_metadata: LabelTranslationMetadata | None = None,
) -> None:
    """Initialize serializers, label maps, and tokenizer state."""
    self.tokenizer = tokenizer
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    self.dataset = layoutformerpp_dataset_slug(normalized_dataset)
    self.task = str(normalized_task)
    self.add_sep_token = add_sep_token
    self.x_grid = x_grid
    self.y_grid = y_grid
    labels = labels_for_dataset(normalized_dataset)
    public_id2label = (
        {int(key): str(value) for key, value in id2label.items()}
        if id2label is not None
        else dict(enumerate(labels))
    )
    canonical_translation = label_translation_for_dataset(normalized_dataset)
    if normalized_dataset is DatasetName.rico25 and public_id2label != dict(
        RICO25_LABEL_TRANSLATION.public_id2label
    ):
        raise ValueError("RICO25 public id2label must match the canonical map")

    translation = build_label_translation(
        public_id2label,
        canonical_translation.sequence_id2label,
    )
    self.id2label = dict(translation.public_id2label)
    self.public_id2label = dict(translation.public_id2label)
    self.public_label2id = {
        normalize_label_name(value): key
        for key, value in self.public_id2label.items()
    }
    self.sequence_id2label = dict(translation.sequence_id2label)
    self.public_to_sequence = dict(translation.public_to_sequence)
    self.sequence_to_public = dict(translation.sequence_to_public)
    self.label_translation_metadata = (
        translation.metadata()
        if label_translation_metadata is None
        else validate_label_translation_metadata(
            label_translation_metadata,
            translation,
        )
    )
    self.label_translation_sha256 = translation.sha256
    self.internal_id2label = {idx: f"label_{idx}" for idx in self.sequence_id2label}
    self.serializer = T5LayoutSequence(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    self.gen_t_serializer = T5LayoutSequenceForGenT(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    self.gen_r_serializer = T5LayoutSequenceForGenR(
        self.internal_id2label, add_sep_token=add_sep_token
    )
    super().__init__(tokenizer=tokenizer)

from_config classmethod

from_config(
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask
    | ConditionType
    | str = DEFAULT_TASK,
    *,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
) -> "LayoutFormerPPProcessor"

Construct processor and tokenizer without external files.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
@classmethod
def from_config(
    cls,
    dataset: DatasetName | str = DEFAULT_DATASET,
    task: LayoutFormerPPTask | ConditionType | str = DEFAULT_TASK,
    *,
    add_sep_token: bool = True,
    x_grid: int = 128,
    y_grid: int = 128,
    id2label: dict[int, str] | None = None,
) -> "LayoutFormerPPProcessor":
    """Construct processor and tokenizer without external files."""
    normalized_dataset = normalize_layoutformerpp_dataset(dataset)
    normalized_task = normalize_layoutformerpp_task(task)
    labels = labels_for_dataset(normalized_dataset)
    tokens = build_default_tokens(labels, task=normalized_task, grid=x_grid)
    tokenizer = LayoutFormerPPTokenizer(tokens=tokens)
    return cls(
        tokenizer=tokenizer,
        dataset=normalized_dataset,
        task=normalized_task,
        add_sep_token=add_sep_token,
        x_grid=x_grid,
        y_grid=y_grid,
        id2label=id2label,
    )

normalize_condition_type

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

Normalize public condition aliases.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
194
195
196
197
198
199
200
201
202
203
204
205
206
def normalize_condition_type(
    self, condition_type: ConditionType | str
) -> SupportedConditionType:
    """Normalize public condition aliases."""
    try:
        condition = normalize_common_condition_type(condition_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported condition_type: {condition_type}") from exc

    if condition not in SUPPORTED_CONDITIONS:
        raise ValueError(f"Unsupported condition_type: {condition_type}")

    return cast(SupportedConditionType, condition)

__call__

__call__(
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[Tensor, "batch elements"]
    | list[list[bool]]
    | list[bool]
    | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[Tensor, "batch relations relation_attrs"]
    | None = None,
    batch_size: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Build tokenized model inputs for a public condition.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
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
def __call__(
    self,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: list[list[int | str]]
    | Int[torch.Tensor, "batch elements"]
    | None = None,
    bbox: LayoutFormerPPBBoxInput = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | list[list[bool]]
    | list[bool]
    | None = None,
    relations: list[list[tuple[int, int, int, int, int]]]
    | Int[torch.Tensor, "batch relations relation_attrs"]
    | None = None,
    batch_size: int | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Build tokenized model inputs for a public condition."""
    condition = self.normalize_condition_type(condition_type)
    batch_size = batch_size or (len(labels) if labels is not None else 1)
    row_lengths = (
        [len(row) for row in labels] if labels is not None else [0] * batch_size
    )
    prepared_mask = self._prepare_mask(
        mask,
        batch_size=batch_size,
        row_lengths=row_lengths,
    )
    internal_labels = self._prepare_labels(labels, batch_size, prepared_mask)
    internal_bbox = self._prepare_bbox(
        bbox,
        labels=internal_labels,
        box_format=box_format,
        mask=prepared_mask,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    texts: list[str] = []
    for idx in range(batch_size):
        if condition is ConditionType.unconditional:
            texts.append("")
        elif condition is ConditionType.label:
            texts.append(
                self.gen_t_serializer.build_input_seq(
                    "gen_t", internal_labels[idx], internal_bbox[idx]
                )
            )
        elif condition is ConditionType.label_size:
            texts.append(
                self.gen_t_serializer.build_input_seq(
                    "gen_ts", internal_labels[idx], internal_bbox[idx]
                )
            )
        elif condition is ConditionType.relation:
            item_relations = [] if relations is None else relations[idx]
            if isinstance(item_relations, torch.Tensor):
                item_relations = cast(
                    list[tuple[int, int, int, int, int]],
                    [
                        tuple(int(value) for value in row)
                        for row in item_relations.tolist()
                    ],
                )
            texts.append(
                self.gen_r_serializer.build_input_seq(
                    internal_labels[idx], item_relations
                )
            )
        elif condition is ConditionType.completion:
            texts.append(
                self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
            )
        elif condition is ConditionType.refinement:
            texts.append(
                self.serializer.build_seq(internal_labels[idx], internal_bbox[idx])
            )
        else:
            raise ValueError(f"Unsupported condition_type: {condition}")

    encoded = self.tokenizer.encode_text(texts, add_eos=True, add_bos=False)
    if return_tensors != "pt":
        raise ValueError("Only return_tensors='pt' is supported")

    return BatchEncoding(encoded)

post_process_layouts

post_process_layouts(
    sequences: Int[Tensor, "batch tokens"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    output_type: OutputType | str = OutputType.dataclass,
    return_tensors: Literal["pt"] = "pt",
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict

Parse generated token ids to the common layout output schema.

Source code in models/layoutformerpp/src/layoutformerpp/processing_layoutformerpp.py
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
def post_process_layouts(
    self,
    sequences: Int[torch.Tensor, "batch tokens"],
    *,
    box_format: BoxFormat | str = BoxFormat.xywh,
    output_type: OutputType | str = OutputType.dataclass,
    return_tensors: Literal["pt"] = "pt",
) -> LayoutGenerationOutput | LayoutFormerPPOutputDict:
    """Parse generated token ids to the common layout output schema."""
    texts = self.tokenizer.batch_decode(sequences, skip_special_tokens=True)
    parsed = [self.serializer.parse_seq(text.strip()) for text in texts]
    recognized: list[ParsedLayout | None] = []
    dropped_count = 0
    for item in parsed:
        if item is None:
            recognized.append(None)
            continue

        public_labels: list[int] = []
        public_boxes: list[list[int]] = []
        for label, box in zip(item.labels, item.bbox, strict=True):
            if label not in self.sequence_to_public:
                # Original tasks/refinement.py::T5LayoutSequence.parse_seq keeps
                # error label 0 and its parsed, possibly shifted box.
                # tasks/ugen.py uses that parser.
                # Drop it because the public schema has no unknown category.
                dropped_count += 1
                continue

            public_labels.append(self.sequence_to_public[label])
            public_boxes.append(box)

        recognized.append(ParsedLayout(labels=public_labels, bbox=public_boxes))

    if dropped_count:
        logger.warning(
            "Dropped %d unrecognized-label element%s from LayoutFormer++ output.",
            dropped_count,
            "" if dropped_count == 1 else "s",
        )

    max_len = max(
        (len(item.labels) for item in recognized if item is not None),
        default=0,
    )
    if max_len == 0:
        max_len = 1
    label_rows: list[Int[torch.Tensor, "elements"]] = []
    bbox_rows: list[Int[torch.Tensor, "elements 4"]] = []
    mask_rows: list[Bool[torch.Tensor, "elements"]] = []
    for item in recognized:
        if item is None or not item.labels:
            labels = torch.zeros(max_len, dtype=torch.long)
            boxes = torch.zeros(max_len, 4, dtype=torch.long)
            mask = torch.zeros(max_len, dtype=torch.bool)
        else:
            labels = torch.tensor(item.labels, dtype=torch.long)
            boxes = torch.tensor(item.bbox, dtype=torch.long)
            mask = torch.ones(len(labels), dtype=torch.bool)
            if len(labels) < max_len:
                pad = max_len - len(labels)
                labels = torch.nn.functional.pad(labels, (0, pad))
                boxes = torch.nn.functional.pad(boxes, (0, 0, 0, pad))
                mask = torch.nn.functional.pad(mask, (0, pad))
        label_rows.append(labels)
        bbox_rows.append(boxes)
        mask_rows.append(mask)
    bbox_ids = torch.stack(bbox_rows)
    normalized_box_format = normalize_box_format(box_format)
    bbox = discrete_ltwh_to_public(
        bbox_ids,
        box_format=normalized_box_format,
        x_grid=self.x_grid,
        y_grid=self.y_grid,
    )
    output = LayoutGenerationOutput(
        bbox=bbox.float(),
        labels=torch.stack(label_rows).long(),
        mask=torch.stack(mask_rows).bool(),
        id2label=dict(self.public_id2label),
        sequences=sequences.long(),
        intermediates={
            "generated_text": texts,
            "box_format": normalized_box_format,
        },
    )
    try:
        normalized_output_type = (
            output_type
            if isinstance(output_type, OutputType)
            else OutputType(output_type)
        )
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

    if normalized_output_type is OutputType.dict:
        return dict(output)
    if normalized_output_type is not OutputType.dataclass:
        assert_never(normalized_output_type)
    if return_tensors != "pt":
        raise ValueError("Only return_tensors='pt' is supported")

    return output

serialization

Task serializers for LayoutFormer++.

RelationType

Bases: StrEnum

Supported LayoutFormer++ relation token names.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
19
20
21
22
23
24
25
26
27
28
29
class RelationType(StrEnum):
    """Supported LayoutFormer++ relation token names."""

    smaller = auto()
    equal = auto()
    larger = auto()
    top = auto()
    center = auto()
    bottom = auto()
    left = auto()
    right = auto()

ParsedLayout dataclass

Parsed discrete layout sequence.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
35
36
37
38
39
40
@dataclass
class ParsedLayout:
    """Parsed discrete layout sequence."""

    labels: list[int]
    bbox: list[list[int]]

T5LayoutSequence

Serialize labels and discrete ltwh bboxes as LayoutFormer++ text.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
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
class T5LayoutSequence:
    """Serialize labels and discrete ltwh bboxes as LayoutFormer++ text."""

    def __init__(self, id2label: dict[int, str], *, add_sep_token: bool = True) -> None:
        """Initialize label lookup tables used by the serializer."""
        self.id2label = id2label
        self.label2id = {label.lower().strip(): idx for idx, label in id2label.items()}
        self.add_sep_token = add_sep_token
        self.error_label_id = 0

    def build_seq(self, labels: list[int], bbox: list[list[int]]) -> str:
        """Build full `label x y w h` sequence."""
        tokens: list[str] = []
        for idx, label_id in enumerate(labels):
            tokens.append(self.id2label[int(label_id)].lower())
            tokens.extend(str(int(value)) for value in bbox[idx])
            if self.add_sep_token and idx < len(labels) - 1:
                tokens.append(SEP_TOKEN)
        return " ".join(tokens)

    def parse_seq(self, output: str) -> ParsedLayout | None:
        """Parse generated text into labels and integer boxes."""
        labels: list[int] = []
        bbox: list[list[int]] = []
        if self.add_sep_token:
            text = output.strip()
            if text:
                text = f"{text} {SEP_TOKEN}"
            pattern = rf"(([\w\-/\s]+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s\{SEP_TOKEN})"
            for match in re.findall(pattern, text):
                label = match[1].strip()
                labels.append(self.label2id.get(label, self.error_label_id))
                bbox.append([int(match[2 + i]) for i in range(4)])
        else:
            tokens = output.split()
            idx = 0
            while idx < len(tokens):
                label_tokens: list[str] = []
                box_tokens: list[int] = []
                while idx < len(tokens) and not tokens[idx].isdigit():
                    label_tokens.append(tokens[idx])
                    idx += 1
                while idx < len(tokens) and tokens[idx].isdigit():
                    box_tokens.append(int(tokens[idx]))
                    idx += 1
                if label_tokens and len(box_tokens) == 4:
                    labels.append(
                        self.label2id.get(
                            " ".join(label_tokens).strip(), self.error_label_id
                        )
                    )
                    bbox.append(box_tokens)
                else:
                    return None
        if not labels:
            return None
        return ParsedLayout(labels=labels, bbox=bbox)

__init__

__init__(
    id2label: dict[int, str], *, add_sep_token: bool = True
) -> None

Initialize label lookup tables used by the serializer.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
46
47
48
49
50
51
def __init__(self, id2label: dict[int, str], *, add_sep_token: bool = True) -> None:
    """Initialize label lookup tables used by the serializer."""
    self.id2label = id2label
    self.label2id = {label.lower().strip(): idx for idx, label in id2label.items()}
    self.add_sep_token = add_sep_token
    self.error_label_id = 0

build_seq

build_seq(labels: list[int], bbox: list[list[int]]) -> str

Build full label x y w h sequence.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
53
54
55
56
57
58
59
60
61
def build_seq(self, labels: list[int], bbox: list[list[int]]) -> str:
    """Build full `label x y w h` sequence."""
    tokens: list[str] = []
    for idx, label_id in enumerate(labels):
        tokens.append(self.id2label[int(label_id)].lower())
        tokens.extend(str(int(value)) for value in bbox[idx])
        if self.add_sep_token and idx < len(labels) - 1:
            tokens.append(SEP_TOKEN)
    return " ".join(tokens)

parse_seq

parse_seq(output: str) -> ParsedLayout | None

Parse generated text into labels and integer boxes.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
def parse_seq(self, output: str) -> ParsedLayout | None:
    """Parse generated text into labels and integer boxes."""
    labels: list[int] = []
    bbox: list[list[int]] = []
    if self.add_sep_token:
        text = output.strip()
        if text:
            text = f"{text} {SEP_TOKEN}"
        pattern = rf"(([\w\-/\s]+)\s(\d+)\s(\d+)\s(\d+)\s(\d+)\s\{SEP_TOKEN})"
        for match in re.findall(pattern, text):
            label = match[1].strip()
            labels.append(self.label2id.get(label, self.error_label_id))
            bbox.append([int(match[2 + i]) for i in range(4)])
    else:
        tokens = output.split()
        idx = 0
        while idx < len(tokens):
            label_tokens: list[str] = []
            box_tokens: list[int] = []
            while idx < len(tokens) and not tokens[idx].isdigit():
                label_tokens.append(tokens[idx])
                idx += 1
            while idx < len(tokens) and tokens[idx].isdigit():
                box_tokens.append(int(tokens[idx]))
                idx += 1
            if label_tokens and len(box_tokens) == 4:
                labels.append(
                    self.label2id.get(
                        " ".join(label_tokens).strip(), self.error_label_id
                    )
                )
                bbox.append(box_tokens)
            else:
                return None
    if not labels:
        return None
    return ParsedLayout(labels=labels, bbox=bbox)

T5LayoutSequenceForGenT

Bases: T5LayoutSequence

Serializer for gen_t and gen_ts conditions.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
class T5LayoutSequenceForGenT(T5LayoutSequence):
    """Serializer for `gen_t` and `gen_ts` conditions."""

    def build_input_seq(
        self,
        task: LayoutFormerPPTask | str,
        labels: list[int],
        bbox: list[list[int]],
        *,
        add_unk_for_label: bool = False,
        add_unk_for_label_size: bool = False,
    ) -> str:
        """Build task input from labels and optional width/height constraints."""
        normalized_task = normalize_layoutformerpp_task(task)
        tokens: list[str] = []
        for idx, label_id in enumerate(labels):
            tokens.append(self.id2label[int(label_id)].lower())
            if normalized_task is LayoutFormerPPTask.gen_t:
                if add_unk_for_label:
                    tokens.extend(["<unk>", "<unk>", "<unk>", "<unk>"])
            elif normalized_task is LayoutFormerPPTask.gen_ts:
                if add_unk_for_label_size:
                    tokens.extend(["<unk>", "<unk>"])
                tokens.extend(str(int(value)) for value in bbox[idx][2:])
            else:
                raise ValueError(f"Unsupported gen_t serializer task: {task}")

            if self.add_sep_token and idx < len(labels) - 1:
                tokens.append(SEP_TOKEN)
        return " ".join(tokens)

build_input_seq

build_input_seq(
    task: LayoutFormerPPTask | str,
    labels: list[int],
    bbox: list[list[int]],
    *,
    add_unk_for_label: bool = False,
    add_unk_for_label_size: bool = False,
) -> str

Build task input from labels and optional width/height constraints.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
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
def build_input_seq(
    self,
    task: LayoutFormerPPTask | str,
    labels: list[int],
    bbox: list[list[int]],
    *,
    add_unk_for_label: bool = False,
    add_unk_for_label_size: bool = False,
) -> str:
    """Build task input from labels and optional width/height constraints."""
    normalized_task = normalize_layoutformerpp_task(task)
    tokens: list[str] = []
    for idx, label_id in enumerate(labels):
        tokens.append(self.id2label[int(label_id)].lower())
        if normalized_task is LayoutFormerPPTask.gen_t:
            if add_unk_for_label:
                tokens.extend(["<unk>", "<unk>", "<unk>", "<unk>"])
        elif normalized_task is LayoutFormerPPTask.gen_ts:
            if add_unk_for_label_size:
                tokens.extend(["<unk>", "<unk>"])
            tokens.extend(str(int(value)) for value in bbox[idx][2:])
        else:
            raise ValueError(f"Unsupported gen_t serializer task: {task}")

        if self.add_sep_token and idx < len(labels) - 1:
            tokens.append(SEP_TOKEN)
    return " ".join(tokens)

T5LayoutSequenceForGenR

Bases: T5LayoutSequence

Serializer for relation-conditioned generation.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
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
class T5LayoutSequenceForGenR(T5LayoutSequence):
    """Serializer for relation-conditioned generation."""

    def build_input_seq(
        self,
        labels: list[int],
        relations: list[tuple[int, int, int, int, int]],
        *,
        add_unk_token: bool = False,
        compact: bool = False,
    ) -> str:
        """Build relation-conditioned input sequence."""
        tokens: list[str] = []

        for idx, label_id in enumerate(labels):
            tokens.append(self.id2label[int(label_id)].lower())
            if add_unk_token:
                tokens.extend(["<unk>", "<unk>", "<unk>", "<unk>"])
            if self.add_sep_token and idx < len(labels) - 1:
                tokens.append(SEP_TOKEN)

        tokens.append(REL_BEG_TOKEN)
        for label_j, index_j, label_i, index_i, relation_type in relations:
            tokens.append(f"label_{label_i} index_{index_i}" if label_i else "label_0")

            if not compact:
                tokens.append(REL_ELE_SEP_TOKEN)
            tokens.append(f"relation_{relation_type}")

            if not compact:
                tokens.append(REL_ELE_SEP_TOKEN)
            tokens.append(f"label_{label_j} index_{index_j}" if label_j else "label_0")

            tokens.append(REL_SEP_TOKEN)
        return " ".join(tokens)

build_input_seq

build_input_seq(
    labels: list[int],
    relations: list[tuple[int, int, int, int, int]],
    *,
    add_unk_token: bool = False,
    compact: bool = False,
) -> str

Build relation-conditioned input sequence.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
def build_input_seq(
    self,
    labels: list[int],
    relations: list[tuple[int, int, int, int, int]],
    *,
    add_unk_token: bool = False,
    compact: bool = False,
) -> str:
    """Build relation-conditioned input sequence."""
    tokens: list[str] = []

    for idx, label_id in enumerate(labels):
        tokens.append(self.id2label[int(label_id)].lower())
        if add_unk_token:
            tokens.extend(["<unk>", "<unk>", "<unk>", "<unk>"])
        if self.add_sep_token and idx < len(labels) - 1:
            tokens.append(SEP_TOKEN)

    tokens.append(REL_BEG_TOKEN)
    for label_j, index_j, label_i, index_i, relation_type in relations:
        tokens.append(f"label_{label_i} index_{index_i}" if label_i else "label_0")

        if not compact:
            tokens.append(REL_ELE_SEP_TOKEN)
        tokens.append(f"relation_{relation_type}")

        if not compact:
            tokens.append(REL_ELE_SEP_TOKEN)
        tokens.append(f"label_{label_j} index_{index_j}" if label_j else "label_0")

        tokens.append(REL_SEP_TOKEN)
    return " ".join(tokens)

build_default_tokens

build_default_tokens(
    dataset_labels: tuple[str, ...],
    *,
    task: LayoutFormerPPTask | str,
    grid: int,
    add_sep_token: bool = True,
) -> list[str]

Construct a checkpoint-compatible vocabulary when no vocab.json is available.

Source code in models/layoutformerpp/src/layoutformerpp/serialization.py
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def build_default_tokens(
    dataset_labels: tuple[str, ...],
    *,
    task: LayoutFormerPPTask | str,
    grid: int,
    add_sep_token: bool = True,
) -> list[str]:
    """Construct a checkpoint-compatible vocabulary when no `vocab.json` is available."""
    normalized_task = normalize_layoutformerpp_task(task)
    tokens = [f"label_{idx}" for idx in range(1, len(dataset_labels) + 1)]
    tokens.extend(str(idx) for idx in range(grid))
    if add_sep_token:
        tokens.append(SEP_TOKEN)
    if normalized_task is LayoutFormerPPTask.gen_r:
        tokens.append("label_0")
        tokens.extend(f"relation_{idx}" for idx, _ in enumerate(RELATION_TYPES))
        tokens.extend(f"index_{idx}" for idx in range(1, 21))
        tokens.extend([REL_BEG_TOKEN, REL_SEP_TOKEN, REL_ELE_SEP_TOKEN])
    return tokens

tasks

Shared LayoutFormer++ dataset and checkpoint-task helpers.

LayoutFormerPPTask

Bases: StrEnum

Supported converted LayoutFormer++ checkpoint variants.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
12
13
14
15
16
17
18
19
20
class LayoutFormerPPTask(StrEnum):
    """Supported converted LayoutFormer++ checkpoint variants."""

    ugen = auto()
    gen_t = auto()
    gen_ts = auto()
    gen_r = auto()
    completion = auto()
    refinement = auto()

OutputType

Bases: StrEnum

Supported post-processing return shapes.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
23
24
25
26
27
class OutputType(StrEnum):
    """Supported post-processing return shapes."""

    dataclass = auto()
    dict = auto()

normalize_layoutformerpp_dataset

normalize_layoutformerpp_dataset(
    dataset: DatasetName | str,
) -> DatasetName

Normalize public dataset aliases to a LayoutFormer++ supported dataset.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
57
58
59
60
61
62
63
def normalize_layoutformerpp_dataset(dataset: DatasetName | str) -> DatasetName:
    """Normalize public dataset aliases to a LayoutFormer++ supported dataset."""
    normalized = normalize_dataset_name(dataset)
    if normalized not in SUPPORTED_DATASETS:
        raise ValueError(f"Unsupported LayoutFormer++ dataset: {dataset}")

    return normalized

layoutformerpp_dataset_slug

layoutformerpp_dataset_slug(
    dataset: DatasetName | str,
) -> str

Return the canonical public dataset slug.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
66
67
68
def layoutformerpp_dataset_slug(dataset: DatasetName | str) -> str:
    """Return the canonical public dataset slug."""
    return DATASET_TO_CANONICAL_SLUG[normalize_layoutformerpp_dataset(dataset)]

layoutformerpp_vendor_dataset_slug

layoutformerpp_vendor_dataset_slug(
    dataset: DatasetName | str,
) -> str

Translate a canonical dataset name at an original-code boundary.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
71
72
73
def layoutformerpp_vendor_dataset_slug(dataset: DatasetName | str) -> str:
    """Translate a canonical dataset name at an original-code boundary."""
    return DATASET_TO_VENDOR_SLUG[normalize_layoutformerpp_dataset(dataset)]

normalize_layoutformerpp_task

normalize_layoutformerpp_task(
    task: LayoutFormerPPTask | ConditionType | str,
) -> LayoutFormerPPTask

Normalize checkpoint variant aliases to the internal task enum.

Source code in models/layoutformerpp/src/layoutformerpp/tasks.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
def normalize_layoutformerpp_task(
    task: LayoutFormerPPTask | ConditionType | str,
) -> LayoutFormerPPTask:
    """Normalize checkpoint variant aliases to the internal task enum."""
    if isinstance(task, LayoutFormerPPTask):
        return task
    try:
        return LayoutFormerPPTask(task)
    except ValueError:
        condition = normalize_condition_type(task)
    try:
        return DEFAULT_TASK_FOR_CONDITION[condition]
    except KeyError as exc:
        raise ValueError(f"Unsupported LayoutFormer++ task: {task}") from exc

tokenization_layoutformerpp

Tokenizer for LayoutFormer++ layout strings.

LayoutFormerPPTokenizer

Bases: WhitespaceTokenizerMixin, PreTrainedTokenizer

Whitespace tokenizer backed by the released vocab.json format.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
class LayoutFormerPPTokenizer(WhitespaceTokenizerMixin, PreTrainedTokenizer):
    """Whitespace tokenizer backed by the released `vocab.json` format."""

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

    def __init__(
        self,
        vocab_file: str | None = None,
        tokens: list[str] | None = None,
        x_grid: int = 128,
        y_grid: int = 128,
        bbox_order: BoxFormat | str = BoxFormat.ltwh,
        bos_token: str = "<bos>",
        eos_token: str = "<eos>",
        pad_token: str = "<pad>",
        sep_token: str = "<sep>",
        unk_token: str = "<unk>",
        model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
        padding_side: str = "right",
        truncation_side: str = "right",
        clean_up_tokenization_spaces: bool = False,
        added_tokens_decoder: dict[int | str, str] | None = None,
        backend: str = "custom",
        tokenizer_file: str | None = None,
        name_or_path: str = "",
        is_local: bool = False,
        local_files_only: bool = False,
        processor_class: str | None = None,
    ) -> None:
        """Initialize a tokenizer from a vocab file or synthetic token list."""
        _ = (backend, tokenizer_file, is_local, local_files_only, processor_class)
        self.x_grid = x_grid
        self.y_grid = y_grid
        self.bbox_order = str(normalize_box_format(bbox_order))
        self._token2id, self._id2token = build_token_maps(
            vocab_file=vocab_file,
            tokens=tokens,
            base_tokens=("<bos>", "<eos>", "<pad>", "<sep>", "<unk>"),
        )
        tokenizer_kwargs: dict[str, object] = {
            "bos_token": bos_token,
            "eos_token": eos_token,
            "pad_token": pad_token,
            "sep_token": sep_token,
            "unk_token": unk_token,
            "model_max_length": model_max_length,
            "padding_side": padding_side,
            "truncation_side": truncation_side,
            "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
            "backend": backend,
            "name_or_path": name_or_path,
        }
        if added_tokens_decoder is not None:
            tokenizer_kwargs["added_tokens_decoder"] = added_tokens_decoder
        super().__init__(**tokenizer_kwargs)

    def save_vocabulary(
        self, save_directory: str, filename_prefix: str | None = None
    ) -> tuple[str]:
        """Save `vocab.json` in checkpoint-compatible token-to-id format."""
        return save_json_vocabulary(
            save_directory=save_directory,
            filename="vocab.json",
            data=self._token2id,
            filename_prefix=filename_prefix,
        )

    def save_pretrained(
        self,
        save_directory: str | PathLike[str],
        legacy_format: bool | None = None,
        filename_prefix: str | None = None,
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> tuple[str, ...]:
        """Save tokenizer files plus LayoutFormer++ tokenizer metadata."""
        _ = kwargs
        paths = super().save_pretrained(
            str(save_directory),
            legacy_format=legacy_format,
            filename_prefix=filename_prefix,
            push_to_hub=push_to_hub,
        )
        metadata = {
            "x_grid": self.x_grid,
            "y_grid": self.y_grid,
            "bbox_order": self.bbox_order,
        }
        with (Path(save_directory) / "layoutformerpp_tokenizer_config.json").open(
            "w"
        ) as f:
            json.dump(metadata, f, indent=2, sort_keys=True)
        return paths

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        x_grid: int | None = None,
        y_grid: int | None = None,
        bbox_order: BoxFormat | str | None = None,
    ) -> "LayoutFormerPPTokenizer":
        """Load tokenizer and LayoutFormer++ metadata."""
        path = Path(pretrained_model_name_or_path)
        metadata_path = path / "layoutformerpp_tokenizer_config.json"
        metadata: dict[str, object] = {}
        if metadata_path.exists():
            with metadata_path.open() as f:
                metadata = json.load(f)
        if x_grid is not None:
            metadata["x_grid"] = x_grid
        if y_grid is not None:
            metadata["y_grid"] = y_grid
        if bbox_order is not None:
            metadata["bbox_order"] = bbox_order
        return cast(
            "LayoutFormerPPTokenizer",
            super().from_pretrained(
                str(pretrained_model_name_or_path),
                cache_dir=cache_dir,
                force_download=force_download,
                local_files_only=local_files_only,
                token=token,
                revision=revision,
                **metadata,
            ),
        )

    def encode_text(
        self, text: str | list[str], *, add_eos: bool = True, add_bos: bool = False
    ) -> BatchEncoding:
        """Tokenize reference-style text while matching the original EOS/BOS behavior."""
        if isinstance(text, str):
            texts = [text]
        else:
            texts = text
        normalized = []
        for item in texts:
            tokens = item.strip().split()
            if add_eos:
                tokens.append(self.eos_token)
            if add_bos:
                tokens.insert(0, self.bos_token)
            normalized.append(" ".join(tokens))
        return self(
            normalized, padding=True, add_special_tokens=False, return_tensors="pt"
        )

__init__

__init__(
    vocab_file: str | None = None,
    tokens: list[str] | None = None,
    x_grid: int = 128,
    y_grid: int = 128,
    bbox_order: BoxFormat | str = BoxFormat.ltwh,
    bos_token: str = "<bos>",
    eos_token: str = "<eos>",
    pad_token: str = "<pad>",
    sep_token: str = "<sep>",
    unk_token: str = "<unk>",
    model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
    padding_side: str = "right",
    truncation_side: str = "right",
    clean_up_tokenization_spaces: bool = False,
    added_tokens_decoder: dict[int | str, str]
    | None = None,
    backend: str = "custom",
    tokenizer_file: str | None = None,
    name_or_path: str = "",
    is_local: bool = False,
    local_files_only: bool = False,
    processor_class: str | None = None,
) -> None

Initialize a tokenizer from a vocab file or synthetic token list.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def __init__(
    self,
    vocab_file: str | None = None,
    tokens: list[str] | None = None,
    x_grid: int = 128,
    y_grid: int = 128,
    bbox_order: BoxFormat | str = BoxFormat.ltwh,
    bos_token: str = "<bos>",
    eos_token: str = "<eos>",
    pad_token: str = "<pad>",
    sep_token: str = "<sep>",
    unk_token: str = "<unk>",
    model_max_length: int = DEFAULT_MODEL_MAX_LENGTH,
    padding_side: str = "right",
    truncation_side: str = "right",
    clean_up_tokenization_spaces: bool = False,
    added_tokens_decoder: dict[int | str, str] | None = None,
    backend: str = "custom",
    tokenizer_file: str | None = None,
    name_or_path: str = "",
    is_local: bool = False,
    local_files_only: bool = False,
    processor_class: str | None = None,
) -> None:
    """Initialize a tokenizer from a vocab file or synthetic token list."""
    _ = (backend, tokenizer_file, is_local, local_files_only, processor_class)
    self.x_grid = x_grid
    self.y_grid = y_grid
    self.bbox_order = str(normalize_box_format(bbox_order))
    self._token2id, self._id2token = build_token_maps(
        vocab_file=vocab_file,
        tokens=tokens,
        base_tokens=("<bos>", "<eos>", "<pad>", "<sep>", "<unk>"),
    )
    tokenizer_kwargs: dict[str, object] = {
        "bos_token": bos_token,
        "eos_token": eos_token,
        "pad_token": pad_token,
        "sep_token": sep_token,
        "unk_token": unk_token,
        "model_max_length": model_max_length,
        "padding_side": padding_side,
        "truncation_side": truncation_side,
        "clean_up_tokenization_spaces": clean_up_tokenization_spaces,
        "backend": backend,
        "name_or_path": name_or_path,
    }
    if added_tokens_decoder is not None:
        tokenizer_kwargs["added_tokens_decoder"] = added_tokens_decoder
    super().__init__(**tokenizer_kwargs)

save_vocabulary

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

Save vocab.json in checkpoint-compatible token-to-id format.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
78
79
80
81
82
83
84
85
86
87
def save_vocabulary(
    self, save_directory: str, filename_prefix: str | None = None
) -> tuple[str]:
    """Save `vocab.json` in checkpoint-compatible token-to-id format."""
    return save_json_vocabulary(
        save_directory=save_directory,
        filename="vocab.json",
        data=self._token2id,
        filename_prefix=filename_prefix,
    )

save_pretrained

save_pretrained(
    save_directory: str | PathLike[str],
    legacy_format: bool | None = None,
    filename_prefix: str | None = None,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> tuple[str, ...]

Save tokenizer files plus LayoutFormer++ tokenizer metadata.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
 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
def save_pretrained(
    self,
    save_directory: str | PathLike[str],
    legacy_format: bool | None = None,
    filename_prefix: str | None = None,
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> tuple[str, ...]:
    """Save tokenizer files plus LayoutFormer++ tokenizer metadata."""
    _ = kwargs
    paths = super().save_pretrained(
        str(save_directory),
        legacy_format=legacy_format,
        filename_prefix=filename_prefix,
        push_to_hub=push_to_hub,
    )
    metadata = {
        "x_grid": self.x_grid,
        "y_grid": self.y_grid,
        "bbox_order": self.bbox_order,
    }
    with (Path(save_directory) / "layoutformerpp_tokenizer_config.json").open(
        "w"
    ) as f:
        json.dump(metadata, f, indent=2, sort_keys=True)
    return paths

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    x_grid: int | None = None,
    y_grid: int | None = None,
    bbox_order: BoxFormat | str | None = None,
) -> "LayoutFormerPPTokenizer"

Load tokenizer and LayoutFormer++ metadata.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    x_grid: int | None = None,
    y_grid: int | None = None,
    bbox_order: BoxFormat | str | None = None,
) -> "LayoutFormerPPTokenizer":
    """Load tokenizer and LayoutFormer++ metadata."""
    path = Path(pretrained_model_name_or_path)
    metadata_path = path / "layoutformerpp_tokenizer_config.json"
    metadata: dict[str, object] = {}
    if metadata_path.exists():
        with metadata_path.open() as f:
            metadata = json.load(f)
    if x_grid is not None:
        metadata["x_grid"] = x_grid
    if y_grid is not None:
        metadata["y_grid"] = y_grid
    if bbox_order is not None:
        metadata["bbox_order"] = bbox_order
    return cast(
        "LayoutFormerPPTokenizer",
        super().from_pretrained(
            str(pretrained_model_name_or_path),
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **metadata,
        ),
    )

encode_text

encode_text(
    text: str | list[str],
    *,
    add_eos: bool = True,
    add_bos: bool = False,
) -> BatchEncoding

Tokenize reference-style text while matching the original EOS/BOS behavior.

Source code in models/layoutformerpp/src/layoutformerpp/tokenization_layoutformerpp.py
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
def encode_text(
    self, text: str | list[str], *, add_eos: bool = True, add_bos: bool = False
) -> BatchEncoding:
    """Tokenize reference-style text while matching the original EOS/BOS behavior."""
    if isinstance(text, str):
        texts = [text]
    else:
        texts = text
    normalized = []
    for item in texts:
        tokens = item.strip().split()
        if add_eos:
            tokens.append(self.eos_token)
        if add_bos:
            tokens.insert(0, self.bos_token)
        normalized.append(" ".join(tokens))
    return self(
        normalized, padding=True, add_special_tokens=False, return_tensors="pt"
    )

training

Package-local LayoutFormer++ training entry points.

lightning_module

PyTorch Lightning module for LayoutFormer++ training.

LayoutFormerPPTrainingConfig

Bases: TypedDict

LightningCLI-safe constructor values for the runtime model config.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class LayoutFormerPPTrainingConfig(TypedDict, total=False):
    """LightningCLI-safe constructor values for the runtime model config."""

    dataset: str
    task: str
    vocab_size: int
    max_position_embeddings: int
    d_model: int
    encoder_layers: int
    decoder_layers: int
    encoder_attention_heads: int
    decoder_attention_heads: int
    dim_feedforward: int
    dropout: float
    share_embedding: bool

LayoutFormerPPPreOptimizerTrace dataclass

Fixed-batch values produced before a training optimizer mutation.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
76
77
78
79
80
81
82
83
84
85
86
87
88
89
@dataclass(frozen=True)
class LayoutFormerPPPreOptimizerTrace:
    """Fixed-batch values produced before a training optimizer mutation."""

    input_ids: Int[torch.Tensor, "batch tokens"]
    attention_mask: Bool[torch.Tensor, "batch tokens"] | None
    decoder_input_ids: Int[torch.Tensor, "batch target_tokens"]
    task_ids: Int[torch.Tensor, "batch"] | None
    encoder_memory: Float[torch.Tensor, "source_tokens batch channels"]
    decoder_hidden_state: Float[torch.Tensor, "target_tokens batch channels"]
    logits: Float[torch.Tensor, "batch target_tokens vocab"]
    per_token_loss: Float[torch.Tensor, "batch target_tokens"]
    pad_only_ce_contribution: Float[torch.Tensor, ""]
    loss: Float[torch.Tensor, ""]

LayoutFormerPPDataModule

Bases: LightningDataModule

Package-local DataModule matching the pinned basic loader behavior.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
class LayoutFormerPPDataModule(LightningDataModule):
    """Package-local DataModule matching the pinned basic loader behavior."""

    def __init__(
        self,
        *,
        recipe_name: str,
        data_root: str | None = None,
        num_workers: int = 0,
        seed: int = 0,
        pin_memory: bool = False,
    ) -> None:
        """Initialize source-root, recipe, and worker-seeding configuration.

        Args:
            recipe_name: Registered recipe used to select dataset and tasks.
            data_root: Processed source-data root, or ``None`` to read an
                environment variable at setup time.
            num_workers: Number of DataLoader worker processes.
            seed: Reserved loader seed recorded with the module configuration.
            pin_memory: Whether DataLoader batches use pinned host memory.
        """
        super().__init__()
        self.recipe = get_training_recipe(recipe_name)
        self.data_root = data_root
        self.num_workers = num_workers
        self.seed = seed
        self.pin_memory = pin_memory
        self.train_dataset: _LayoutFormerPPDataset | None = None
        self.val_dataset: _LayoutFormerPPDataset | None = None
        self.tokenizer = _package_tokenizer(self.recipe)

    def setup(self, stage: str | None = None) -> None:
        """Load the processed train and validation splits for ``stage``."""
        del stage
        root_value = self.data_root or os.environ.get("LAYOUTFORMERPP_DATA_ROOT")
        if not root_value:
            root_value = os.environ.get("LAYOUTFORMERPP_PARITY_DATA_ROOT")

        if not root_value:
            raise RuntimeError(
                "LayoutFormer++ DataModule requires data_root or "
                "LAYOUTFORMERPP_DATA_ROOT"
            )

        root = Path(root_value)
        self.train_dataset = _LayoutFormerPPDataset(
            self.recipe, root, "train", train=True
        )
        self.val_dataset = _LayoutFormerPPDataset(self.recipe, root, "val", train=False)

    def _collate(self, samples: list[_LayoutFormerPPSample]) -> _LayoutFormerPPBatch:
        input_text = [sample["input_text"] for sample in samples]
        output_text = [sample["output_text"] for sample in samples]
        inputs = self.tokenizer.encode_text(input_text, add_eos=True)
        targets = self.tokenizer.encode_text(output_text, add_eos=True)
        return {
            "input_ids": inputs["input_ids"].long(),
            "attention_mask": inputs["attention_mask"].bool(),
            "labels": targets["input_ids"].long(),
            "target_attention_mask": targets["attention_mask"].bool(),
            "task_ids": torch.tensor(
                [int(sample["task_id"]) for sample in samples], dtype=torch.long
            ),
            "names": [sample["name"] for sample in samples],
            "task_names": [sample["task_name"] for sample in samples],
            "input_bytes": [sample["input_bytes"] for sample in samples],
            "output_bytes": [sample["output_bytes"] for sample in samples],
        }

    def train_dataloader(self) -> DataLoader[_LayoutFormerPPSample]:
        """Return the shuffled, drop-last production training loader."""
        if self.train_dataset is None:
            self.setup("fit")

        if self.train_dataset is None:
            raise RuntimeError("train dataset was not initialized")

        return DataLoader(
            self.train_dataset,
            batch_size=self.recipe.batch_size,
            shuffle=True,
            drop_last=True,
            num_workers=self.num_workers,
            pin_memory=self.pin_memory,
            collate_fn=self._collate,
            worker_init_fn=_seed_worker if self.num_workers else None,
            generator=None,
        )

    def val_dataloader(self) -> DataLoader[_LayoutFormerPPSample]:
        """Return the deterministic, drop-last production validation loader."""
        if self.val_dataset is None:
            self.setup("fit")

        if self.val_dataset is None:
            raise RuntimeError("validation dataset was not initialized")

        return DataLoader(
            self.val_dataset,
            batch_size=self.recipe.eval_batch_size,
            shuffle=False,
            drop_last=True,
            num_workers=self.num_workers,
            pin_memory=self.pin_memory,
            collate_fn=self._collate,
            worker_init_fn=_seed_worker if self.num_workers else None,
            generator=None,
        )
__init__
__init__(
    *,
    recipe_name: str,
    data_root: str | None = None,
    num_workers: int = 0,
    seed: int = 0,
    pin_memory: bool = False,
) -> None

Initialize source-root, recipe, and worker-seeding configuration.

Parameters:

Name Type Description Default
recipe_name str

Registered recipe used to select dataset and tasks.

required
data_root str | None

Processed source-data root, or None to read an environment variable at setup time.

None
num_workers int

Number of DataLoader worker processes.

0
seed int

Reserved loader seed recorded with the module configuration.

0
pin_memory bool

Whether DataLoader batches use pinned host memory.

False
Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
def __init__(
    self,
    *,
    recipe_name: str,
    data_root: str | None = None,
    num_workers: int = 0,
    seed: int = 0,
    pin_memory: bool = False,
) -> None:
    """Initialize source-root, recipe, and worker-seeding configuration.

    Args:
        recipe_name: Registered recipe used to select dataset and tasks.
        data_root: Processed source-data root, or ``None`` to read an
            environment variable at setup time.
        num_workers: Number of DataLoader worker processes.
        seed: Reserved loader seed recorded with the module configuration.
        pin_memory: Whether DataLoader batches use pinned host memory.
    """
    super().__init__()
    self.recipe = get_training_recipe(recipe_name)
    self.data_root = data_root
    self.num_workers = num_workers
    self.seed = seed
    self.pin_memory = pin_memory
    self.train_dataset: _LayoutFormerPPDataset | None = None
    self.val_dataset: _LayoutFormerPPDataset | None = None
    self.tokenizer = _package_tokenizer(self.recipe)
setup
setup(stage: str | None = None) -> None

Load the processed train and validation splits for stage.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
def setup(self, stage: str | None = None) -> None:
    """Load the processed train and validation splits for ``stage``."""
    del stage
    root_value = self.data_root or os.environ.get("LAYOUTFORMERPP_DATA_ROOT")
    if not root_value:
        root_value = os.environ.get("LAYOUTFORMERPP_PARITY_DATA_ROOT")

    if not root_value:
        raise RuntimeError(
            "LayoutFormer++ DataModule requires data_root or "
            "LAYOUTFORMERPP_DATA_ROOT"
        )

    root = Path(root_value)
    self.train_dataset = _LayoutFormerPPDataset(
        self.recipe, root, "train", train=True
    )
    self.val_dataset = _LayoutFormerPPDataset(self.recipe, root, "val", train=False)
train_dataloader
train_dataloader() -> DataLoader[_LayoutFormerPPSample]

Return the shuffled, drop-last production training loader.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
def train_dataloader(self) -> DataLoader[_LayoutFormerPPSample]:
    """Return the shuffled, drop-last production training loader."""
    if self.train_dataset is None:
        self.setup("fit")

    if self.train_dataset is None:
        raise RuntimeError("train dataset was not initialized")

    return DataLoader(
        self.train_dataset,
        batch_size=self.recipe.batch_size,
        shuffle=True,
        drop_last=True,
        num_workers=self.num_workers,
        pin_memory=self.pin_memory,
        collate_fn=self._collate,
        worker_init_fn=_seed_worker if self.num_workers else None,
        generator=None,
    )
val_dataloader
val_dataloader() -> DataLoader[_LayoutFormerPPSample]

Return the deterministic, drop-last production validation loader.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
def val_dataloader(self) -> DataLoader[_LayoutFormerPPSample]:
    """Return the deterministic, drop-last production validation loader."""
    if self.val_dataset is None:
        self.setup("fit")

    if self.val_dataset is None:
        raise RuntimeError("validation dataset was not initialized")

    return DataLoader(
        self.val_dataset,
        batch_size=self.recipe.eval_batch_size,
        shuffle=False,
        drop_last=True,
        num_workers=self.num_workers,
        pin_memory=self.pin_memory,
        collate_fn=self._collate,
        worker_init_fn=_seed_worker if self.num_workers else None,
        generator=None,
    )

LayoutFormerPPTrainingModule

Bases: LightningModule

Train the genuine runtime LayoutFormer++ model with faithful static wiring.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
class LayoutFormerPPTrainingModule(LightningModule):
    """Train the genuine runtime LayoutFormer++ model with faithful static wiring."""

    def __init__(
        self,
        *,
        recipe_name: str,
        config: LayoutFormerPPTrainingConfig,
        model: LayoutFormerPPForConditionalGeneration | None = None,
    ) -> None:
        """Initialize one immutable recipe and its runtime model."""
        super().__init__()
        self.recipe: LayoutFormerPPTrainingRecipe = get_training_recipe(recipe_name)
        runtime_config = LayoutFormerPPConfig(
            **cast(_LayoutFormerPPConfigInput, config)
        )
        self._validate_config(runtime_config)
        self.layoutformerpp_config = runtime_config
        self.model = model or LayoutFormerPPForConditionalGeneration(runtime_config)

    def _validate_config(self, config: LayoutFormerPPConfig) -> None:
        expected = self.recipe
        actual = {
            "dataset": config.dataset,
            "condition": config.condition_type,
            "vocab_size": config.vocab_size,
            "max_position_embeddings": config.max_position_embeddings,
        }
        required = {
            "dataset": str(expected.dataset),
            "condition": str(expected.condition),
            "vocab_size": expected.vocab_size,
            "max_position_embeddings": expected.max_position_embeddings,
        }
        if actual != required:
            raise ValueError(
                f"LayoutFormer++ config does not match recipe {expected.name}: "
                f"expected {required}, got {actual}"
            )

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch tokens"],
        attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        decoder_input_ids: Int[torch.Tensor, "batch target_tokens"] | None = None,
        task_ids: Int[torch.Tensor, "batch"] | None = None,
    ) -> Shaped[torch.Tensor, "batch target_tokens vocab"]:
        """Return logits from the owned runtime model."""
        outputs = self.model(
            input_ids=input_ids,
            attention_mask=attention_mask,
            decoder_input_ids=decoder_input_ids,
            task_ids=task_ids,
            return_dict=True,
        )
        return cast(torch.Tensor, outputs.logits)

    def _loss(
        self,
        batch: Mapping[
            str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
        ],
    ) -> Float[torch.Tensor, ""]:
        """Return the package loss from the pre-optimizer trace."""
        return self.pre_optimizer_trace(batch).loss

    def pre_optimizer_trace(
        self,
        batch: Mapping[
            str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
        ],
    ) -> LayoutFormerPPPreOptimizerTrace:
        """Capture package-model inputs, logits, and loss before an optimizer step."""
        input_ids_value = batch["input_ids"]
        labels_value = batch["labels"]
        if not isinstance(input_ids_value, torch.Tensor) or not isinstance(
            labels_value, torch.Tensor
        ):
            raise TypeError("pre_optimizer_trace requires tensor input_ids and labels")

        input_ids = input_ids_value.long()
        labels = labels_value.long()
        attention_mask_value = batch.get("attention_mask")
        attention_mask = (
            attention_mask_value.bool()
            if isinstance(attention_mask_value, torch.Tensor)
            else None
        )
        effective_attention_mask = (
            attention_mask
            if attention_mask is not None
            else input_ids.ne(self.model.pad_token_id)
        )
        task_ids_value = batch.get("task_ids")
        if task_ids_value is not None and not isinstance(task_ids_value, torch.Tensor):
            raise TypeError("task_ids must be a tensor or None")

        task_ids = task_ids_value.long() if task_ids_value is not None else None
        decoder_input_ids = self.model.prepare_decoder_input_ids_from_labels(labels)
        enc_hs, enc_padding_mask = self.model.encode(
            input_ids, ~effective_attention_mask, task_ids
        )
        dec_input = self.model.dec_pos_embedding(
            self.model.dec_embedding(decoder_input_ids).permute(1, 0, 2)
        )
        decoder_hidden_state = self.model.decoder(
            tgt=dec_input,
            memory=enc_hs,
            tgt_mask=generate_square_subsequent_mask(
                dec_input.size(0), dec_input.device
            ),
            memory_key_padding_mask=enc_padding_mask,
        )
        logits = self.model.out(decoder_hidden_state.permute(1, 0, 2))
        per_token_loss = F.cross_entropy(
            logits.reshape(-1, logits.size(-1)),
            labels.reshape(-1),
            reduction="none",
        ).reshape_as(labels)
        pad_mask = labels.eq(self.model.pad_token_id)
        pad_only_ce_contribution = (
            per_token_loss.masked_select(pad_mask).sum() / labels.numel()
        )
        return LayoutFormerPPPreOptimizerTrace(
            input_ids=input_ids,
            attention_mask=attention_mask,
            decoder_input_ids=decoder_input_ids,
            task_ids=task_ids,
            encoder_memory=enc_hs,
            decoder_hidden_state=decoder_hidden_state,
            logits=logits,
            per_token_loss=per_token_loss,
            pad_only_ce_contribution=pad_only_ce_contribution,
            loss=vendor_effective_cross_entropy(logits, labels),
        )

    def training_step(
        self,
        batch: Mapping[
            str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
        ],
        batch_idx: int,
    ) -> Float[torch.Tensor, ""]:
        """Compute the package training loss without altering runtime loss semantics."""
        del batch_idx
        loss = self._loss(batch)
        self.log("train_loss", loss, on_step=True, on_epoch=True)
        return loss

    def validation_step(
        self,
        batch: Mapping[
            str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
        ],
        batch_idx: int,
    ) -> Float[torch.Tensor, ""]:
        """Compute aggregate validation loss used for checkpoint selection."""
        del batch_idx
        loss = self._loss(batch)
        self.log("val_loss", loss, on_step=False, on_epoch=True, prog_bar=True)
        return loss

    def configure_optimizers(self) -> OptimizerLRScheduler:
        """Construct basic-mode Adam and the post-update logarithmic scheduler."""
        optimizer = torch.optim.Adam(
            self.model.parameters(),
            lr=self.recipe.learning_rate,
        )
        scheduler = LayoutFormerPPWarmupLR(
            optimizer,
            warmup_num_steps=self.recipe.warmup_num_steps,
            warmup_max_lr=self.recipe.learning_rate,
        )
        return {
            "optimizer": optimizer,
            "lr_scheduler": {
                "scheduler": scheduler,
                "interval": "step",
                "frequency": 1,
            },
        }

    def lr_scheduler_step(
        self,
        scheduler: LRScheduler,
        metric: float | None,
    ) -> None:
        """Advance exactly once after Lightning completes an optimizer update."""
        del metric
        scheduler.step()
__init__
__init__(
    *,
    recipe_name: str,
    config: LayoutFormerPPTrainingConfig,
    model: LayoutFormerPPForConditionalGeneration
    | None = None,
) -> None

Initialize one immutable recipe and its runtime model.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
def __init__(
    self,
    *,
    recipe_name: str,
    config: LayoutFormerPPTrainingConfig,
    model: LayoutFormerPPForConditionalGeneration | None = None,
) -> None:
    """Initialize one immutable recipe and its runtime model."""
    super().__init__()
    self.recipe: LayoutFormerPPTrainingRecipe = get_training_recipe(recipe_name)
    runtime_config = LayoutFormerPPConfig(
        **cast(_LayoutFormerPPConfigInput, config)
    )
    self._validate_config(runtime_config)
    self.layoutformerpp_config = runtime_config
    self.model = model or LayoutFormerPPForConditionalGeneration(runtime_config)
forward
forward(
    input_ids: Int[Tensor, "batch tokens"],
    attention_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    decoder_input_ids: Int[Tensor, "batch target_tokens"]
    | None = None,
    task_ids: Int[Tensor, "batch"] | None = None,
) -> Shaped[torch.Tensor, "batch target_tokens vocab"]

Return logits from the owned runtime model.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch tokens"],
    attention_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    decoder_input_ids: Int[torch.Tensor, "batch target_tokens"] | None = None,
    task_ids: Int[torch.Tensor, "batch"] | None = None,
) -> Shaped[torch.Tensor, "batch target_tokens vocab"]:
    """Return logits from the owned runtime model."""
    outputs = self.model(
        input_ids=input_ids,
        attention_mask=attention_mask,
        decoder_input_ids=decoder_input_ids,
        task_ids=task_ids,
        return_dict=True,
    )
    return cast(torch.Tensor, outputs.logits)
pre_optimizer_trace
pre_optimizer_trace(
    batch: Mapping[
        str,
        Shaped[Tensor, "..."]
        | list[str]
        | list[bytes]
        | None,
    ],
) -> LayoutFormerPPPreOptimizerTrace

Capture package-model inputs, logits, and loss before an optimizer step.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
def pre_optimizer_trace(
    self,
    batch: Mapping[
        str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
    ],
) -> LayoutFormerPPPreOptimizerTrace:
    """Capture package-model inputs, logits, and loss before an optimizer step."""
    input_ids_value = batch["input_ids"]
    labels_value = batch["labels"]
    if not isinstance(input_ids_value, torch.Tensor) or not isinstance(
        labels_value, torch.Tensor
    ):
        raise TypeError("pre_optimizer_trace requires tensor input_ids and labels")

    input_ids = input_ids_value.long()
    labels = labels_value.long()
    attention_mask_value = batch.get("attention_mask")
    attention_mask = (
        attention_mask_value.bool()
        if isinstance(attention_mask_value, torch.Tensor)
        else None
    )
    effective_attention_mask = (
        attention_mask
        if attention_mask is not None
        else input_ids.ne(self.model.pad_token_id)
    )
    task_ids_value = batch.get("task_ids")
    if task_ids_value is not None and not isinstance(task_ids_value, torch.Tensor):
        raise TypeError("task_ids must be a tensor or None")

    task_ids = task_ids_value.long() if task_ids_value is not None else None
    decoder_input_ids = self.model.prepare_decoder_input_ids_from_labels(labels)
    enc_hs, enc_padding_mask = self.model.encode(
        input_ids, ~effective_attention_mask, task_ids
    )
    dec_input = self.model.dec_pos_embedding(
        self.model.dec_embedding(decoder_input_ids).permute(1, 0, 2)
    )
    decoder_hidden_state = self.model.decoder(
        tgt=dec_input,
        memory=enc_hs,
        tgt_mask=generate_square_subsequent_mask(
            dec_input.size(0), dec_input.device
        ),
        memory_key_padding_mask=enc_padding_mask,
    )
    logits = self.model.out(decoder_hidden_state.permute(1, 0, 2))
    per_token_loss = F.cross_entropy(
        logits.reshape(-1, logits.size(-1)),
        labels.reshape(-1),
        reduction="none",
    ).reshape_as(labels)
    pad_mask = labels.eq(self.model.pad_token_id)
    pad_only_ce_contribution = (
        per_token_loss.masked_select(pad_mask).sum() / labels.numel()
    )
    return LayoutFormerPPPreOptimizerTrace(
        input_ids=input_ids,
        attention_mask=attention_mask,
        decoder_input_ids=decoder_input_ids,
        task_ids=task_ids,
        encoder_memory=enc_hs,
        decoder_hidden_state=decoder_hidden_state,
        logits=logits,
        per_token_loss=per_token_loss,
        pad_only_ce_contribution=pad_only_ce_contribution,
        loss=vendor_effective_cross_entropy(logits, labels),
    )
training_step
training_step(
    batch: Mapping[
        str,
        Shaped[Tensor, "..."]
        | list[str]
        | list[bytes]
        | None,
    ],
    batch_idx: int,
) -> Float[torch.Tensor, ""]

Compute the package training loss without altering runtime loss semantics.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
836
837
838
839
840
841
842
843
844
845
846
847
def training_step(
    self,
    batch: Mapping[
        str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
    ],
    batch_idx: int,
) -> Float[torch.Tensor, ""]:
    """Compute the package training loss without altering runtime loss semantics."""
    del batch_idx
    loss = self._loss(batch)
    self.log("train_loss", loss, on_step=True, on_epoch=True)
    return loss
validation_step
validation_step(
    batch: Mapping[
        str,
        Shaped[Tensor, "..."]
        | list[str]
        | list[bytes]
        | None,
    ],
    batch_idx: int,
) -> Float[torch.Tensor, ""]

Compute aggregate validation loss used for checkpoint selection.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
849
850
851
852
853
854
855
856
857
858
859
860
def validation_step(
    self,
    batch: Mapping[
        str, Shaped[torch.Tensor, "..."] | list[str] | list[bytes] | None
    ],
    batch_idx: int,
) -> Float[torch.Tensor, ""]:
    """Compute aggregate validation loss used for checkpoint selection."""
    del batch_idx
    loss = self._loss(batch)
    self.log("val_loss", loss, on_step=False, on_epoch=True, prog_bar=True)
    return loss
configure_optimizers
configure_optimizers() -> OptimizerLRScheduler

Construct basic-mode Adam and the post-update logarithmic scheduler.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
def configure_optimizers(self) -> OptimizerLRScheduler:
    """Construct basic-mode Adam and the post-update logarithmic scheduler."""
    optimizer = torch.optim.Adam(
        self.model.parameters(),
        lr=self.recipe.learning_rate,
    )
    scheduler = LayoutFormerPPWarmupLR(
        optimizer,
        warmup_num_steps=self.recipe.warmup_num_steps,
        warmup_max_lr=self.recipe.learning_rate,
    )
    return {
        "optimizer": optimizer,
        "lr_scheduler": {
            "scheduler": scheduler,
            "interval": "step",
            "frequency": 1,
        },
    }
lr_scheduler_step
lr_scheduler_step(
    scheduler: LRScheduler, metric: float | None
) -> None

Advance exactly once after Lightning completes an optimizer update.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
882
883
884
885
886
887
888
889
def lr_scheduler_step(
    self,
    scheduler: LRScheduler,
    metric: float | None,
) -> None:
    """Advance exactly once after Lightning completes an optimizer update."""
    del metric
    scheduler.step()

vendor_effective_cross_entropy

vendor_effective_cross_entropy(
    logits: Float[Tensor, "batch tokens vocab"],
    targets: Int[Tensor, "batch tokens"],
) -> Float[torch.Tensor, ""]

Compute the pad-inclusive cross-entropy used by the reference recipe.

Source code in models/layoutformerpp/src/layoutformerpp/training/lightning_module.py
 92
 93
 94
 95
 96
 97
 98
 99
100
def vendor_effective_cross_entropy(
    logits: Float[torch.Tensor, "batch tokens vocab"],
    targets: Int[torch.Tensor, "batch tokens"],
) -> Float[torch.Tensor, ""]:
    """Compute the pad-inclusive cross-entropy used by the reference recipe."""
    return F.cross_entropy(
        logits.reshape(-1, logits.size(-1)),
        targets.reshape(-1),
    )

parity

Static-state comparison helpers for LayoutFormer++ training evidence.

StaticStateComparison dataclass

Exhaustive state-key and parameter-count comparison.

Source code in models/layoutformerpp/src/layoutformerpp/training/parity.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
@dataclass(frozen=True, slots=True)
class StaticStateComparison:
    """Exhaustive state-key and parameter-count comparison."""

    reference_parameter_count: int
    package_parameter_count: int
    missing_package_keys: tuple[str, ...]
    extra_package_keys: tuple[str, ...]
    reference_state_sha256: str
    package_state_sha256: str

    @property
    def passed(self) -> bool:
        """Return whether topology and copied state agree exhaustively."""
        return (
            self.reference_parameter_count == self.package_parameter_count
            and not self.missing_package_keys
            and not self.extra_package_keys
            and self.reference_state_sha256 == self.package_state_sha256
        )
passed property
passed: bool

Return whether topology and copied state agree exhaustively.

parameter_count

parameter_count(module: Module) -> int

Count unique parameter storage exposed by a module.

Source code in models/layoutformerpp/src/layoutformerpp/training/parity.py
35
36
37
def parameter_count(module: torch.nn.Module) -> int:
    """Count unique parameter storage exposed by a module."""
    return sum(parameter.numel() for parameter in module.parameters())

state_dict_sha256

state_dict_sha256(
    state_dict: Mapping[str, Shaped[Tensor, "..."]],
) -> str

Hash sorted state names, tensor metadata, and raw CPU bytes.

Source code in models/layoutformerpp/src/layoutformerpp/training/parity.py
40
41
42
43
44
45
46
47
48
49
50
51
52
def state_dict_sha256(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> str:
    """Hash sorted state names, tensor metadata, and raw CPU bytes."""
    digest = hashlib.sha256()
    for name, tensor in sorted(state_dict.items()):
        contiguous = tensor.detach().cpu().contiguous()
        digest.update(name.encode("utf-8"))
        digest.update(str(contiguous.dtype).encode("ascii"))
        digest.update(str(tuple(contiguous.shape)).encode("ascii"))
        digest.update(contiguous.view(torch.uint8).numpy().tobytes())

    return digest.hexdigest()

compare_static_state

compare_static_state(
    reference: Module, package: Module
) -> StaticStateComparison

Compare exhaustive keys, parameter counts, and copied tensor bytes.

Source code in models/layoutformerpp/src/layoutformerpp/training/parity.py
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
def compare_static_state(
    reference: torch.nn.Module,
    package: torch.nn.Module,
) -> StaticStateComparison:
    """Compare exhaustive keys, parameter counts, and copied tensor bytes."""
    reference_state = reference.state_dict()
    package_state = package.state_dict()
    return StaticStateComparison(
        reference_parameter_count=parameter_count(reference),
        package_parameter_count=parameter_count(package),
        missing_package_keys=tuple(sorted(set(reference_state) - set(package_state))),
        extra_package_keys=tuple(sorted(set(package_state) - set(reference_state))),
        reference_state_sha256=state_dict_sha256(reference_state),
        package_state_sha256=state_dict_sha256(package_state),
    )

recipes

Immutable training recipes for LayoutFormer++ checkpoint families.

LayoutFormerPPTrainingRecipe dataclass

Static training identity for one dataset and condition family.

Source code in models/layoutformerpp/src/layoutformerpp/training/recipes.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
@dataclass(frozen=True, slots=True)
class LayoutFormerPPTrainingRecipe:
    """Static training identity for one dataset and condition family."""

    name: str
    dataset: DatasetName
    condition: ConditionType
    tasks: tuple[LayoutFormerPPTask, ...]
    task_ids: tuple[int, ...]
    eval_tasks: tuple[LayoutFormerPPTask, ...]
    epochs: int
    batch_size: int
    eval_batch_size: int
    max_position_embeddings: int
    decode_max_length: int
    warmup_num_steps: int
    eval_seed: int
    eval_interval: int
    vocab_size: int
    serialization_flags: frozenset[str]
    partition_buckets: tuple[int, ...] = ()
    learning_rate: float = 1e-4
    num_layers: int = 8
    attention_heads: int = 8
    d_model: int = 512
    dropout: float = 0.1
    max_num_elements: int = 20
    discrete_x_grid: int = 128
    discrete_y_grid: int = 128
    gradient_accumulation: int = 1
    trainer_mode: str = "basic"
    precision: str = "32-true"
    loss_mode: str = "vendor_effective_cross_entropy"
    scheduler_timing: str = "post_optimizer_step"
    use_gradient_clipping: bool = False
    use_ema: bool = False
    use_amp: bool = False

    @property
    def label_translation_sha256(self) -> str:
        """Return the semantic label-map fingerprint for this recipe."""
        return label_translation_for_dataset(self.dataset).sha256

    @property
    def canonical_hub_id(self) -> str:
        """Return the canonical public checkpoint identity."""
        suffix = self.condition.replace("_", "-")
        return f"creative-graphic-design/layoutformerpp-{self.dataset}-{suffix}"
label_translation_sha256 property
label_translation_sha256: str

Return the semantic label-map fingerprint for this recipe.

canonical_hub_id property
canonical_hub_id: str

Return the canonical public checkpoint identity.

get_training_recipe

get_training_recipe(
    name: str,
) -> LayoutFormerPPTrainingRecipe

Return one faithful recipe by its canonical config stem.

Source code in models/layoutformerpp/src/layoutformerpp/training/recipes.py
372
373
374
375
376
377
def get_training_recipe(name: str) -> LayoutFormerPPTrainingRecipe:
    """Return one faithful recipe by its canonical config stem."""
    try:
        return TRAINING_RECIPES_BY_NAME[name]
    except KeyError as exc:
        raise ValueError(f"Unknown LayoutFormer++ training recipe: {name}") from exc

scheduler

Optimizer-update scheduler used by LayoutFormer++ training.

LayoutFormerPPWarmupLR

Bases: LRScheduler

Logarithmic warmup with an explicit no-eager-step initial state.

Source code in models/layoutformerpp/src/layoutformerpp/training/scheduler.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class LayoutFormerPPWarmupLR(LRScheduler):
    """Logarithmic warmup with an explicit no-eager-step initial state."""

    def __init__(
        self,
        optimizer: Optimizer,
        *,
        warmup_num_steps: int,
        warmup_min_lr: float = 0.0,
        warmup_max_lr: float = 1e-4,
        last_batch_iteration: int = -1,
    ) -> None:
        """Capture optimizer state without changing its initial learning rate."""
        if warmup_num_steps <= 1:
            raise ValueError("warmup_num_steps must be greater than one")

        self.warmup_num_steps = warmup_num_steps
        self.warmup_min_lr = warmup_min_lr
        self.warmup_max_lr = warmup_max_lr
        self.last_batch_iteration = last_batch_iteration
        super().__init__(optimizer, last_epoch=last_batch_iteration)
        self._last_lr = [float(group["lr"]) for group in self.optimizer.param_groups]

    def _initial_step(self) -> None:
        """Initialize scheduler bookkeeping without changing optimizer LR."""
        self._step_count = 0

    def _lr_at(self, index: int) -> float:
        if index >= self.warmup_num_steps:
            return self.warmup_max_lr

        ratio = math.log(index + 1) / math.log(self.warmup_num_steps)
        return self.warmup_min_lr + (self.warmup_max_lr - self.warmup_min_lr) * ratio

    def step(self, epoch: int | None = None) -> None:
        """Advance once after a successful optimizer update."""
        self._step_count += 1
        if epoch is None:
            self.last_batch_iteration += 1
        else:
            self.last_batch_iteration = epoch

        self.last_epoch = self.last_batch_iteration
        lr = self._lr_at(self.last_batch_iteration)
        self._last_lr = [lr for _ in self.optimizer.param_groups]
        for group in self.optimizer.param_groups:
            group["lr"] = lr

    def get_lr(self) -> list[float | Shaped[torch.Tensor, ""]]:
        """Return learning rates for the current original-code index."""
        return [
            self._lr_at(self.last_batch_iteration) for _ in self.optimizer.param_groups
        ]
__init__
__init__(
    optimizer: Optimizer,
    *,
    warmup_num_steps: int,
    warmup_min_lr: float = 0.0,
    warmup_max_lr: float = 0.0001,
    last_batch_iteration: int = -1,
) -> None

Capture optimizer state without changing its initial learning rate.

Source code in models/layoutformerpp/src/layoutformerpp/training/scheduler.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def __init__(
    self,
    optimizer: Optimizer,
    *,
    warmup_num_steps: int,
    warmup_min_lr: float = 0.0,
    warmup_max_lr: float = 1e-4,
    last_batch_iteration: int = -1,
) -> None:
    """Capture optimizer state without changing its initial learning rate."""
    if warmup_num_steps <= 1:
        raise ValueError("warmup_num_steps must be greater than one")

    self.warmup_num_steps = warmup_num_steps
    self.warmup_min_lr = warmup_min_lr
    self.warmup_max_lr = warmup_max_lr
    self.last_batch_iteration = last_batch_iteration
    super().__init__(optimizer, last_epoch=last_batch_iteration)
    self._last_lr = [float(group["lr"]) for group in self.optimizer.param_groups]
step
step(epoch: int | None = None) -> None

Advance once after a successful optimizer update.

Source code in models/layoutformerpp/src/layoutformerpp/training/scheduler.py
47
48
49
50
51
52
53
54
55
56
57
58
59
def step(self, epoch: int | None = None) -> None:
    """Advance once after a successful optimizer update."""
    self._step_count += 1
    if epoch is None:
        self.last_batch_iteration += 1
    else:
        self.last_batch_iteration = epoch

    self.last_epoch = self.last_batch_iteration
    lr = self._lr_at(self.last_batch_iteration)
    self._last_lr = [lr for _ in self.optimizer.param_groups]
    for group in self.optimizer.param_groups:
        group["lr"] = lr
get_lr
get_lr() -> list[float | Shaped[torch.Tensor, '']]

Return learning rates for the current original-code index.

Source code in models/layoutformerpp/src/layoutformerpp/training/scheduler.py
61
62
63
64
65
def get_lr(self) -> list[float | Shaped[torch.Tensor, ""]]:
    """Return learning rates for the current original-code index."""
    return [
        self._lr_at(self.last_batch_iteration) for _ in self.optimizer.param_groups
    ]