Skip to content

Layout action

LayoutAction conversion package.

LayoutActionConfig

Bases: PretrainedConfig

Architecture and tokenizer metadata for LayoutAction checkpoints.

Parameters:

Name Type Description Default
dataset_name str

Dataset slug. rico and layout_action_rico13 map to the released RICO13 label order.

'rico13'
id2label Mapping[int, str] | Mapping[str, str] | None

Dataset-local label mapping.

None
precision int

Coordinate precision; checkpoint defaults to 8 bits.

8
max_elements int | None

Maximum number of layout elements.

None
block_size int | None

GPT context length. Defaults to released max token length.

None
vocab_size int | None

Token vocabulary size. Defaults to the reference formula.

None
n_layer int

Number of GPT blocks.

6
n_head int

Attention heads.

8
n_embd int

Hidden size.

512
embd_pdrop float

Embedding dropout.

0.1
resid_pdrop float

Residual dropout.

0.1
attn_pdrop float

Attention dropout.

0.1
default_sampling LayoutActionSamplingMode | str

Default pipeline sampling mode.

top_k
default_top_k int

Default top-k value.

5
default_temperature float

Default sampling temperature.

1.0
original_dataset_name str | None

Original dataset name.

None
original_asset_manifest Mapping[str, str | int | list[str] | dict[str, AssetManifestFile]] | None

Optional asset manifest.

None
kwargs str | int | float | bool | None

Additional PretrainedConfig fields.

{}

Examples:

>>> config = LayoutActionConfig(dataset_name="publaynet")
>>> config.bos_token_id == config.vocab_size - 3
True
Source code in models/layout-action/src/layout_action/configuration_layout_action.py
 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
class LayoutActionConfig(PretrainedConfig):
    """Architecture and tokenizer metadata for LayoutAction checkpoints.

    Args:
        dataset_name: Dataset slug. ``rico`` and ``layout_action_rico13`` map to
            the released RICO13 label order.
        id2label: Dataset-local label mapping.
        precision: Coordinate precision; checkpoint defaults to 8 bits.
        max_elements: Maximum number of layout elements.
        block_size: GPT context length. Defaults to released max token length.
        vocab_size: Token vocabulary size. Defaults to the reference formula.
        n_layer: Number of GPT blocks.
        n_head: Attention heads.
        n_embd: Hidden size.
        embd_pdrop: Embedding dropout.
        resid_pdrop: Residual dropout.
        attn_pdrop: Attention dropout.
        default_sampling: Default pipeline sampling mode.
        default_top_k: Default top-k value.
        default_temperature: Default sampling temperature.
        original_dataset_name: Original dataset name.
        original_asset_manifest: Optional asset manifest.
        kwargs: Additional ``PretrainedConfig`` fields.

    Examples:
        >>> config = LayoutActionConfig(dataset_name="publaynet")
        >>> config.bos_token_id == config.vocab_size - 3
        True
    """

    model_type = "layout-action"

    def __init__(
        self,
        *,
        dataset_name: str = "rico13",
        id2label: Mapping[int, str] | Mapping[str, str] | None = None,
        precision: int = 8,
        max_elements: int | None = None,
        block_size: int | None = None,
        vocab_size: int | None = None,
        n_layer: int = 6,
        n_head: int = 8,
        n_embd: int = 512,
        embd_pdrop: float = 0.1,
        resid_pdrop: float = 0.1,
        attn_pdrop: float = 0.1,
        default_sampling: LayoutActionSamplingMode
        | str = LayoutActionSamplingMode.top_k,
        default_top_k: int = 5,
        default_temperature: float = 1.0,
        original_dataset_name: str | None = None,
        original_asset_manifest: Mapping[
            str, str | int | list[str] | dict[str, AssetManifestFile]
        ]
        | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize LayoutAction metadata and derived token ids."""
        _ = (model_type, transformers_version)
        for derived_key in (
            "bos_token_id",
            "eos_token_id",
            "pad_token_id",
            "label2id",
            "size",
            "element_token_width",
            "max_token_length",
            "no_value_token_id",
            "label_token_offset",
            "copy_token_id",
            "margin_token_id",
            "generate_token_id",
            "no_obj_token_id",
        ):
            kwargs.pop(derived_key, None)
        _ = kwargs
        super().__init__()
        dataset = normalize_vendor_dataset_name(dataset_name)
        labels = layout_action_labels(dataset)
        normalized_id2label = (
            {int(key): str(value) for key, value in id2label.items()}
            if id2label is not None
            else dict(enumerate(labels))
        )
        self.dataset_name = dataset
        self.precision = int(precision)
        self.max_elements = int(
            max_elements
            if max_elements is not None
            else max_elements_for_layout_action_dataset(dataset)
        )
        self.n_layer = int(n_layer)
        self.n_head = int(n_head)
        self.n_embd = int(n_embd)
        self.embd_pdrop = float(embd_pdrop)
        self.resid_pdrop = float(resid_pdrop)
        self.attn_pdrop = float(attn_pdrop)
        self.default_sampling = str(normalize_sampling_mode(default_sampling))
        self.default_top_k = int(default_top_k)
        self.default_temperature = float(default_temperature)
        self.original_dataset_name = original_dataset_name or dataset
        self.original_asset_manifest = dict(original_asset_manifest or {})
        self.id2label: dict[int, str] = normalized_id2label
        self.label2id: dict[str, int] = {
            value: key for key, value in self.id2label.items()
        }
        self.size: int = 2**self.precision
        self.element_token_width: int = ELEMENT_TOKEN_WIDTH
        self.max_token_length: int = self.max_elements * self.element_token_width + 2
        self.block_size = int(
            block_size if block_size is not None else self.max_token_length
        )
        self.no_value_token_id: int = self.size
        self.label_token_offset: int = self.size + 1
        self.copy_token_id: int = self.label_token_offset + len(self.id2label)
        self.margin_token_id: int = self.copy_token_id + 1
        self.generate_token_id: int = self.margin_token_id + 1
        self.no_obj_token_id: int = self.generate_token_id + 1
        resolved_vocab_size = (
            int(vocab_size)
            if vocab_size is not None
            else self.size + 1 + len(self.id2label) + 3 + 1 + self.max_elements + 3
        )
        self.vocab_size = resolved_vocab_size
        self.bos_token_id = self.vocab_size - 3
        self.eos_token_id = self.vocab_size - 2
        self.pad_token_id = self.vocab_size - 1

    def label_token_id(self, label_id: int) -> int:
        """Return the synthetic token id for a dataset-local label id."""
        id2label = cast(dict[int, str], self.id2label)
        if label_id not in id2label:
            raise ValueError(f"Unknown LayoutAction label id: {label_id}")

        return self.label_token_offset + int(label_id)

    def label_id_from_token(self, token_id: int) -> int | None:
        """Return a dataset-local label id for a label token id."""
        id2label = cast(dict[int, str], self.id2label)
        label_id = int(token_id) - self.label_token_offset
        if 0 <= label_id < len(id2label):
            return label_id
        return None

    def object_token_id(self, back_reference: int) -> int:
        """Return the token id for a previous-object back reference."""
        if not 1 <= back_reference <= self.max_elements:
            raise ValueError("back_reference must be in [1, max_elements]")

        return self.no_obj_token_id + back_reference

    def back_reference_from_token(self, token_id: int) -> int | None:
        """Return a previous-object back reference from a token id."""
        value = int(token_id) - self.no_obj_token_id
        if 1 <= value <= self.max_elements:
            return value
        return None

__init__

__init__(
    *,
    dataset_name: str = "rico13",
    id2label: Mapping[int, str]
    | Mapping[str, str]
    | None = None,
    precision: int = 8,
    max_elements: int | None = None,
    block_size: int | None = None,
    vocab_size: int | None = None,
    n_layer: int = 6,
    n_head: int = 8,
    n_embd: int = 512,
    embd_pdrop: float = 0.1,
    resid_pdrop: float = 0.1,
    attn_pdrop: float = 0.1,
    default_sampling: LayoutActionSamplingMode
    | str = LayoutActionSamplingMode.top_k,
    default_top_k: int = 5,
    default_temperature: float = 1.0,
    original_dataset_name: str | None = None,
    original_asset_manifest: Mapping[
        str,
        str
        | int
        | list[str]
        | dict[str, AssetManifestFile],
    ]
    | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize LayoutAction metadata and derived token ids.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def __init__(
    self,
    *,
    dataset_name: str = "rico13",
    id2label: Mapping[int, str] | Mapping[str, str] | None = None,
    precision: int = 8,
    max_elements: int | None = None,
    block_size: int | None = None,
    vocab_size: int | None = None,
    n_layer: int = 6,
    n_head: int = 8,
    n_embd: int = 512,
    embd_pdrop: float = 0.1,
    resid_pdrop: float = 0.1,
    attn_pdrop: float = 0.1,
    default_sampling: LayoutActionSamplingMode
    | str = LayoutActionSamplingMode.top_k,
    default_top_k: int = 5,
    default_temperature: float = 1.0,
    original_dataset_name: str | None = None,
    original_asset_manifest: Mapping[
        str, str | int | list[str] | dict[str, AssetManifestFile]
    ]
    | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize LayoutAction metadata and derived token ids."""
    _ = (model_type, transformers_version)
    for derived_key in (
        "bos_token_id",
        "eos_token_id",
        "pad_token_id",
        "label2id",
        "size",
        "element_token_width",
        "max_token_length",
        "no_value_token_id",
        "label_token_offset",
        "copy_token_id",
        "margin_token_id",
        "generate_token_id",
        "no_obj_token_id",
    ):
        kwargs.pop(derived_key, None)
    _ = kwargs
    super().__init__()
    dataset = normalize_vendor_dataset_name(dataset_name)
    labels = layout_action_labels(dataset)
    normalized_id2label = (
        {int(key): str(value) for key, value in id2label.items()}
        if id2label is not None
        else dict(enumerate(labels))
    )
    self.dataset_name = dataset
    self.precision = int(precision)
    self.max_elements = int(
        max_elements
        if max_elements is not None
        else max_elements_for_layout_action_dataset(dataset)
    )
    self.n_layer = int(n_layer)
    self.n_head = int(n_head)
    self.n_embd = int(n_embd)
    self.embd_pdrop = float(embd_pdrop)
    self.resid_pdrop = float(resid_pdrop)
    self.attn_pdrop = float(attn_pdrop)
    self.default_sampling = str(normalize_sampling_mode(default_sampling))
    self.default_top_k = int(default_top_k)
    self.default_temperature = float(default_temperature)
    self.original_dataset_name = original_dataset_name or dataset
    self.original_asset_manifest = dict(original_asset_manifest or {})
    self.id2label: dict[int, str] = normalized_id2label
    self.label2id: dict[str, int] = {
        value: key for key, value in self.id2label.items()
    }
    self.size: int = 2**self.precision
    self.element_token_width: int = ELEMENT_TOKEN_WIDTH
    self.max_token_length: int = self.max_elements * self.element_token_width + 2
    self.block_size = int(
        block_size if block_size is not None else self.max_token_length
    )
    self.no_value_token_id: int = self.size
    self.label_token_offset: int = self.size + 1
    self.copy_token_id: int = self.label_token_offset + len(self.id2label)
    self.margin_token_id: int = self.copy_token_id + 1
    self.generate_token_id: int = self.margin_token_id + 1
    self.no_obj_token_id: int = self.generate_token_id + 1
    resolved_vocab_size = (
        int(vocab_size)
        if vocab_size is not None
        else self.size + 1 + len(self.id2label) + 3 + 1 + self.max_elements + 3
    )
    self.vocab_size = resolved_vocab_size
    self.bos_token_id = self.vocab_size - 3
    self.eos_token_id = self.vocab_size - 2
    self.pad_token_id = self.vocab_size - 1

label_token_id

label_token_id(label_id: int) -> int

Return the synthetic token id for a dataset-local label id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
190
191
192
193
194
195
196
def label_token_id(self, label_id: int) -> int:
    """Return the synthetic token id for a dataset-local label id."""
    id2label = cast(dict[int, str], self.id2label)
    if label_id not in id2label:
        raise ValueError(f"Unknown LayoutAction label id: {label_id}")

    return self.label_token_offset + int(label_id)

label_id_from_token

label_id_from_token(token_id: int) -> int | None

Return a dataset-local label id for a label token id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
198
199
200
201
202
203
204
def label_id_from_token(self, token_id: int) -> int | None:
    """Return a dataset-local label id for a label token id."""
    id2label = cast(dict[int, str], self.id2label)
    label_id = int(token_id) - self.label_token_offset
    if 0 <= label_id < len(id2label):
        return label_id
    return None

object_token_id

object_token_id(back_reference: int) -> int

Return the token id for a previous-object back reference.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
206
207
208
209
210
211
def object_token_id(self, back_reference: int) -> int:
    """Return the token id for a previous-object back reference."""
    if not 1 <= back_reference <= self.max_elements:
        raise ValueError("back_reference must be in [1, max_elements]")

    return self.no_obj_token_id + back_reference

back_reference_from_token

back_reference_from_token(token_id: int) -> int | None

Return a previous-object back reference from a token id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
213
214
215
216
217
218
def back_reference_from_token(self, token_id: int) -> int | None:
    """Return a previous-object back reference from a token id."""
    value = int(token_id) - self.no_obj_token_id
    if 1 <= value <= self.max_elements:
        return value
    return None

LayoutActionSamplingMode

Bases: StrEnum

Supported token sampling modes.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
39
40
41
42
43
44
class LayoutActionSamplingMode(StrEnum):
    """Supported token sampling modes."""

    greedy = auto()
    multinomial = auto()
    top_k = auto()

StateDictKeyReport dataclass

One source-to-target state-dict mapping result.

Source code in models/layout-action/src/layout_action/conversion.py
20
21
22
23
24
25
26
27
@dataclass(frozen=True)
class StateDictKeyReport:
    """One source-to-target state-dict mapping result."""

    source_key: str
    target_key: str
    source_shape: tuple[int, ...]
    loaded: bool

LayoutActionSamplingConfig dataclass

Sampling parameters for LayoutAction token generation.

Parameters:

Name Type Description Default
mode LayoutActionSamplingMode

Greedy, multinomial, or top-k sampling.

top_k
temperature float

Positive logit temperature.

1.0
top_k int | None

Optional top-k crop size.

5

Examples:

>>> str(LayoutActionSamplingConfig().mode)
'top_k'
Source code in models/layout-action/src/layout_action/generation_layout_action.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@dataclass(frozen=True)
class LayoutActionSamplingConfig:
    """Sampling parameters for LayoutAction token generation.

    Args:
        mode: Greedy, multinomial, or top-k sampling.
        temperature: Positive logit temperature.
        top_k: Optional top-k crop size.

    Examples:
        >>> str(LayoutActionSamplingConfig().mode)
        'top_k'
    """

    mode: LayoutActionSamplingMode = LayoutActionSamplingMode.top_k
    temperature: float = 1.0
    top_k: int | None = 5

    @classmethod
    def from_values(
        cls,
        *,
        mode: LayoutActionSamplingMode | str,
        temperature: float,
        top_k: int | None,
    ) -> "LayoutActionSamplingConfig":
        """Build a normalized sampling config from public values."""
        return cls(
            mode=normalize_sampling_mode(mode),
            temperature=float(temperature),
            top_k=None if top_k is None else int(top_k),
        )

from_values classmethod

from_values(
    *,
    mode: LayoutActionSamplingMode | str,
    temperature: float,
    top_k: int | None,
) -> "LayoutActionSamplingConfig"

Build a normalized sampling config from public values.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@classmethod
def from_values(
    cls,
    *,
    mode: LayoutActionSamplingMode | str,
    temperature: float,
    top_k: int | None,
) -> "LayoutActionSamplingConfig":
    """Build a normalized sampling config from public values."""
    return cls(
        mode=normalize_sampling_mode(mode),
        temperature=float(temperature),
        top_k=None if top_k is None else int(top_k),
    )

LayoutActionForCausalLM

Bases: PreTrainedModel

Transformers PreTrainedModel for LayoutAction token prediction.

Parameters:

Name Type Description Default
config LayoutActionConfig

LayoutAction architecture and vocabulary metadata.

required

Examples:

>>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
>>> model = LayoutActionForCausalLM(config)
>>> out = model(torch.tensor([[config.bos_token_id]]))
>>> out.logits.shape[-1] == config.vocab_size
True
Source code in models/layout-action/src/layout_action/modeling_layout_action.py
 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
class LayoutActionForCausalLM(PreTrainedModel):
    """Transformers ``PreTrainedModel`` for LayoutAction token prediction.

    Args:
        config: LayoutAction architecture and vocabulary metadata.

    Examples:
        >>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
        >>> model = LayoutActionForCausalLM(config)
        >>> out = model(torch.tensor([[config.bos_token_id]]))
        >>> out.logits.shape[-1] == config.vocab_size
        True
    """

    config_class = LayoutActionConfig
    base_model_prefix = "layout_action"
    main_input_name = "input_ids"
    _tied_weights_keys: dict[str, str] = {}

    def __init__(self, config: LayoutActionConfig) -> None:
        """Initialize checkpoint-compatible GPT modules."""
        super().__init__(config)
        self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd)
        self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd))
        self.drop = nn.Dropout(config.embd_pdrop)
        mask = torch.tril(torch.ones(config.block_size, config.block_size)).view(
            1, 1, config.block_size, config.block_size
        )
        self.blocks = nn.ModuleList(
            [LayoutActionBlock(config, mask) for _ in range(config.n_layer)]
        )
        self.ln_f = nn.LayerNorm(config.n_embd)
        self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
        self.block_size = config.block_size
        self.all_tied_weights_keys = dict(self._tied_weights_keys)
        self.post_init()

    def get_block_size(self) -> int:
        """Return the maximum context length."""
        return self.block_size

    def get_input_embeddings(self) -> nn.Embedding:
        """Return token embeddings."""
        return self.tok_emb

    def set_input_embeddings(self, value: nn.Embedding) -> None:
        """Replace token embeddings."""
        self.tok_emb = value

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch sequence"],
        attention_mask: Bool[torch.Tensor, "batch sequence"] | None = None,
        labels: Int[torch.Tensor, "batch sequence"] | None = None,
        return_dict: bool | None = None,
        output_hidden_states: bool | None = None,
        output_attentions: bool | None = None,
    ) -> CausalLMOutputWithCrossAttentions | tuple[Shaped[torch.Tensor, "..."], ...]:
        """Run a standard causal language-model forward pass.

        Args:
            input_ids: Token ids shaped ``(batch, sequence)``.
            attention_mask: Accepted for Transformers compatibility; causal
                masking follows the checkpoint implementation.
            labels: Optional next-token labels.
            return_dict: Whether to return a dataclass output.
            output_hidden_states: Include final hidden states.
            output_attentions: Accepted for API compatibility; attentions are
                not materialized by the checkpoint-compatible blocks.

        Returns:
            Causal LM output or tuple.

        Raises:
            ValueError: If sequence length exceeds ``block_size``.
        """
        _ = (attention_mask, output_attentions)
        use_return_dict = (
            self.config.use_return_dict if return_dict is None else return_dict
        )
        batch, steps = input_ids.size()
        if steps > self.block_size:
            raise ValueError("Cannot forward; model block size is exhausted.")

        token_embeddings = self.tok_emb(input_ids)
        position_embeddings = self.pos_emb[:, :steps, :]
        hidden_states = self.drop(token_embeddings + position_embeddings)
        for block in self.blocks:
            hidden_states = block(hidden_states)
        hidden_states = self.ln_f(hidden_states)
        logits = self.head(hidden_states)
        loss = None
        if labels is not None:
            target = labels.masked_fill(labels == self.config.pad_token_id, -100)
            loss = F.cross_entropy(
                logits.view(-1, logits.size(-1)),
                target.view(-1),
                ignore_index=-100,
            )
        if not use_return_dict:
            values: tuple[Shaped[torch.Tensor, "..."], ...]
            values = (logits,) if loss is None else (loss, logits)
            if output_hidden_states:
                values = (*values, hidden_states)
            return values
        return CausalLMOutputWithCrossAttentions(
            loss=cast(torch.FloatTensor | None, loss),
            logits=logits,
            hidden_states=(hidden_states,) if output_hidden_states else None,
        )

    @torch.no_grad()
    def generate(
        self,
        input_ids: Int[torch.Tensor, "batch sequence"],
        *,
        max_new_tokens: int,
        temperature: float = 1.0,
        top_k: int | None = None,
        do_sample: bool = False,
        forced_token_ids: Int[torch.Tensor, "batch sequence"] | None = None,
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch sequence"]:
        """Generate token ids with the reference sampling loop.

        Args:
            input_ids: Prompt token ids.
            max_new_tokens: Number of new tokens.
            temperature: Sampling temperature.
            top_k: Optional top-k crop size.
            do_sample: Whether to use multinomial sampling. If ``False``, greedy
                decoding is used.
            forced_token_ids: Optional ids to force at each generation step.
            generator: Optional torch generator for multinomial sampling.

        Returns:
            Prompt plus generated token ids.
        """
        mode = (
            LayoutActionSamplingMode.top_k
            if do_sample and top_k is not None
            else LayoutActionSamplingMode.multinomial
            if do_sample
            else LayoutActionSamplingMode.greedy
        )
        return sample_action_tokens(
            self,
            input_ids,
            max_new_tokens=max_new_tokens,
            sampling=LayoutActionSamplingConfig(
                mode=mode,
                temperature=temperature,
                top_k=top_k,
            ),
            forced_token_ids=forced_token_ids,
            generator=generator,
        )

__init__

__init__(config: LayoutActionConfig) -> None

Initialize checkpoint-compatible GPT modules.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def __init__(self, config: LayoutActionConfig) -> None:
    """Initialize checkpoint-compatible GPT modules."""
    super().__init__(config)
    self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd)
    self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd))
    self.drop = nn.Dropout(config.embd_pdrop)
    mask = torch.tril(torch.ones(config.block_size, config.block_size)).view(
        1, 1, config.block_size, config.block_size
    )
    self.blocks = nn.ModuleList(
        [LayoutActionBlock(config, mask) for _ in range(config.n_layer)]
    )
    self.ln_f = nn.LayerNorm(config.n_embd)
    self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
    self.block_size = config.block_size
    self.all_tied_weights_keys = dict(self._tied_weights_keys)
    self.post_init()

get_block_size

get_block_size() -> int

Return the maximum context length.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
127
128
129
def get_block_size(self) -> int:
    """Return the maximum context length."""
    return self.block_size

get_input_embeddings

get_input_embeddings() -> nn.Embedding

Return token embeddings.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
131
132
133
def get_input_embeddings(self) -> nn.Embedding:
    """Return token embeddings."""
    return self.tok_emb

set_input_embeddings

set_input_embeddings(value: Embedding) -> None

Replace token embeddings.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
135
136
137
def set_input_embeddings(self, value: nn.Embedding) -> None:
    """Replace token embeddings."""
    self.tok_emb = value

forward

forward(
    input_ids: Int[Tensor, "batch sequence"],
    attention_mask: Bool[Tensor, "batch sequence"]
    | None = None,
    labels: Int[Tensor, "batch sequence"] | None = None,
    return_dict: bool | None = None,
    output_hidden_states: bool | None = None,
    output_attentions: bool | None = None,
) -> (
    CausalLMOutputWithCrossAttentions
    | tuple[Shaped[torch.Tensor, "..."], ...]
)

Run a standard causal language-model forward pass.

Parameters:

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

Token ids shaped (batch, sequence).

required
attention_mask Bool[Tensor, 'batch sequence'] | None

Accepted for Transformers compatibility; causal masking follows the checkpoint implementation.

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

Optional next-token labels.

None
return_dict bool | None

Whether to return a dataclass output.

None
output_hidden_states bool | None

Include final hidden states.

None
output_attentions bool | None

Accepted for API compatibility; attentions are not materialized by the checkpoint-compatible blocks.

None

Returns:

Type Description
CausalLMOutputWithCrossAttentions | tuple[Shaped[Tensor, '...'], ...]

Causal LM output or tuple.

Raises:

Type Description
ValueError

If sequence length exceeds block_size.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch sequence"],
    attention_mask: Bool[torch.Tensor, "batch sequence"] | None = None,
    labels: Int[torch.Tensor, "batch sequence"] | None = None,
    return_dict: bool | None = None,
    output_hidden_states: bool | None = None,
    output_attentions: bool | None = None,
) -> CausalLMOutputWithCrossAttentions | tuple[Shaped[torch.Tensor, "..."], ...]:
    """Run a standard causal language-model forward pass.

    Args:
        input_ids: Token ids shaped ``(batch, sequence)``.
        attention_mask: Accepted for Transformers compatibility; causal
            masking follows the checkpoint implementation.
        labels: Optional next-token labels.
        return_dict: Whether to return a dataclass output.
        output_hidden_states: Include final hidden states.
        output_attentions: Accepted for API compatibility; attentions are
            not materialized by the checkpoint-compatible blocks.

    Returns:
        Causal LM output or tuple.

    Raises:
        ValueError: If sequence length exceeds ``block_size``.
    """
    _ = (attention_mask, output_attentions)
    use_return_dict = (
        self.config.use_return_dict if return_dict is None else return_dict
    )
    batch, steps = input_ids.size()
    if steps > self.block_size:
        raise ValueError("Cannot forward; model block size is exhausted.")

    token_embeddings = self.tok_emb(input_ids)
    position_embeddings = self.pos_emb[:, :steps, :]
    hidden_states = self.drop(token_embeddings + position_embeddings)
    for block in self.blocks:
        hidden_states = block(hidden_states)
    hidden_states = self.ln_f(hidden_states)
    logits = self.head(hidden_states)
    loss = None
    if labels is not None:
        target = labels.masked_fill(labels == self.config.pad_token_id, -100)
        loss = F.cross_entropy(
            logits.view(-1, logits.size(-1)),
            target.view(-1),
            ignore_index=-100,
        )
    if not use_return_dict:
        values: tuple[Shaped[torch.Tensor, "..."], ...]
        values = (logits,) if loss is None else (loss, logits)
        if output_hidden_states:
            values = (*values, hidden_states)
        return values
    return CausalLMOutputWithCrossAttentions(
        loss=cast(torch.FloatTensor | None, loss),
        logits=logits,
        hidden_states=(hidden_states,) if output_hidden_states else None,
    )

generate

generate(
    input_ids: Int[Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    temperature: float = 1.0,
    top_k: int | None = None,
    do_sample: bool = False,
    forced_token_ids: Int[Tensor, "batch sequence"]
    | None = None,
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]

Generate token ids with the reference sampling loop.

Parameters:

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

Prompt token ids.

required
max_new_tokens int

Number of new tokens.

required
temperature float

Sampling temperature.

1.0
top_k int | None

Optional top-k crop size.

None
do_sample bool

Whether to use multinomial sampling. If False, greedy decoding is used.

False
forced_token_ids Int[Tensor, 'batch sequence'] | None

Optional ids to force at each generation step.

None
generator Generator | None

Optional torch generator for multinomial sampling.

None

Returns:

Type Description
Int[Tensor, 'batch sequence']

Prompt plus generated token ids.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
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
@torch.no_grad()
def generate(
    self,
    input_ids: Int[torch.Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    temperature: float = 1.0,
    top_k: int | None = None,
    do_sample: bool = False,
    forced_token_ids: Int[torch.Tensor, "batch sequence"] | None = None,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]:
    """Generate token ids with the reference sampling loop.

    Args:
        input_ids: Prompt token ids.
        max_new_tokens: Number of new tokens.
        temperature: Sampling temperature.
        top_k: Optional top-k crop size.
        do_sample: Whether to use multinomial sampling. If ``False``, greedy
            decoding is used.
        forced_token_ids: Optional ids to force at each generation step.
        generator: Optional torch generator for multinomial sampling.

    Returns:
        Prompt plus generated token ids.
    """
    mode = (
        LayoutActionSamplingMode.top_k
        if do_sample and top_k is not None
        else LayoutActionSamplingMode.multinomial
        if do_sample
        else LayoutActionSamplingMode.greedy
    )
    return sample_action_tokens(
        self,
        input_ids,
        max_new_tokens=max_new_tokens,
        sampling=LayoutActionSamplingConfig(
            mode=mode,
            temperature=temperature,
            top_k=top_k,
        ),
        forced_token_ids=forced_token_ids,
        generator=generator,
    )

LayoutActionPipeline

Bases: LayoutGenerationPipeline

Compose a LayoutAction model and processor for layout generation.

Parameters:

Name Type Description Default
model LayoutActionForCausalLM

Converted LayoutAction causal LM.

required
processor LayoutActionProcessor

Matching processor/tokenizer.

required
config LayoutActionConfig | None

Optional root pipeline config. Defaults to model.config.

None

Examples:

>>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
>>> pipe = LayoutActionPipeline(
...     model=LayoutActionForCausalLM(config),
...     processor=LayoutActionProcessor(LayoutActionTokenizer(config)),
...     config=config,
... )
>>> pipe.config.model_type
'layout-action'
Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
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
class LayoutActionPipeline(LayoutGenerationPipeline):
    """Compose a LayoutAction model and processor for layout generation.

    Args:
        model: Converted LayoutAction causal LM.
        processor: Matching processor/tokenizer.
        config: Optional root pipeline config. Defaults to ``model.config``.

    Examples:
        >>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
        >>> pipe = LayoutActionPipeline(
        ...     model=LayoutActionForCausalLM(config),
        ...     processor=LayoutActionProcessor(LayoutActionTokenizer(config)),
        ...     config=config,
        ... )
        >>> pipe.config.model_type
        'layout-action'
    """

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

    config: LayoutActionConfig
    model: LayoutActionForCausalLM
    processor: LayoutActionProcessor

    def __init__(
        self,
        model: LayoutActionForCausalLM,
        processor: LayoutActionProcessor,
        config: LayoutActionConfig | None = None,
    ) -> None:
        """Initialize the pipeline."""
        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],
    ) -> LayoutActionPipeline:
        """Build a pipeline from loaded components."""
        return cls(
            config=cast(LayoutActionConfig, config),
            model=cast(LayoutActionForCausalLM, components["model"]),
            processor=cast(LayoutActionProcessor, 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: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType = "dataclass",
        return_intermediates: bool = False,
        sampling: Literal["greedy", "multinomial", "top_k"] = "top_k",
        temperature: float = 1.0,
        top_k: int | None = 5,
    ) -> LayoutGenerationOutput | LayoutActionOutputDict:  # ty: ignore[invalid-method-override]
        """Generate a layout through the public LayoutAction interface."""
        encoded = self.processor(
            condition_type=condition_type,
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            batch_size=batch_size,
            return_tensors="pt",
        )
        model_device = next(self.model.parameters()).device
        prepared_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
            device=model_device,
        )
        input_ids = encoded["input_ids"].to(model_device)
        forced_token_ids = encoded.get("forced_token_ids")
        if isinstance(forced_token_ids, torch.Tensor):
            forced_token_ids = forced_token_ids.to(model_device)
        max_new_tokens = (
            int(num_inference_steps)
            if num_inference_steps is not None
            else int(encoded["max_new_tokens"])
        )
        was_training = self.model.training
        self.model.eval()
        try:
            sequences = self.model.generate(
                input_ids,
                max_new_tokens=max_new_tokens,
                temperature=temperature,
                top_k=top_k,
                do_sample=sampling != "greedy",
                forced_token_ids=forced_token_ids,
                generator=prepared_generator,
            )
        finally:
            self.model.train(was_training)
        return self.processor.post_process_layouts(
            sequences,
            output_type=output_type,
            return_intermediates=return_intermediates,
        )

__init__

__init__(
    model: LayoutActionForCausalLM,
    processor: LayoutActionProcessor,
    config: LayoutActionConfig | None = None,
) -> None

Initialize the pipeline.

Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
 99
100
101
102
103
104
105
106
107
108
109
def __init__(
    self,
    model: LayoutActionForCausalLM,
    processor: LayoutActionProcessor,
    config: LayoutActionConfig | None = None,
) -> None:
    """Initialize the pipeline."""
    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: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
    sampling: Literal[
        "greedy", "multinomial", "top_k"
    ] = "top_k",
    temperature: float = 1.0,
    top_k: int | None = 5,
) -> LayoutGenerationOutput | LayoutActionOutputDict

Generate a layout through the public LayoutAction interface.

Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
    sampling: Literal["greedy", "multinomial", "top_k"] = "top_k",
    temperature: float = 1.0,
    top_k: int | None = 5,
) -> LayoutGenerationOutput | LayoutActionOutputDict:  # ty: ignore[invalid-method-override]
    """Generate a layout through the public LayoutAction interface."""
    encoded = self.processor(
        condition_type=condition_type,
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        batch_size=batch_size,
        return_tensors="pt",
    )
    model_device = next(self.model.parameters()).device
    prepared_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
        device=model_device,
    )
    input_ids = encoded["input_ids"].to(model_device)
    forced_token_ids = encoded.get("forced_token_ids")
    if isinstance(forced_token_ids, torch.Tensor):
        forced_token_ids = forced_token_ids.to(model_device)
    max_new_tokens = (
        int(num_inference_steps)
        if num_inference_steps is not None
        else int(encoded["max_new_tokens"])
    )
    was_training = self.model.training
    self.model.eval()
    try:
        sequences = self.model.generate(
            input_ids,
            max_new_tokens=max_new_tokens,
            temperature=temperature,
            top_k=top_k,
            do_sample=sampling != "greedy",
            forced_token_ids=forced_token_ids,
            generator=prepared_generator,
        )
    finally:
        self.model.train(was_training)
    return self.processor.post_process_layouts(
        sequences,
        output_type=output_type,
        return_intermediates=return_intermediates,
    )

LayoutActionProcessor

Bases: ProcessorMixin

Prepare LayoutAction prompts and decode generated action sequences.

Parameters:

Name Type Description Default
tokenizer LayoutActionTokenizer

LayoutAction tokenizer.

required

Examples:

>>> processor = LayoutActionProcessor(LayoutActionTokenizer(LayoutActionConfig(max_elements=1)))
>>> encoded = processor(condition_type="unconditional")
>>> encoded["input_ids"].shape
torch.Size([1, 1])
Source code in models/layout-action/src/layout_action/processing_layout_action.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
class LayoutActionProcessor(ProcessorMixin):
    """Prepare LayoutAction prompts and decode generated action sequences.

    Args:
        tokenizer: LayoutAction tokenizer.

    Examples:
        >>> processor = LayoutActionProcessor(LayoutActionTokenizer(LayoutActionConfig(max_elements=1)))
        >>> encoded = processor(condition_type="unconditional")
        >>> encoded["input_ids"].shape
        torch.Size([1, 1])
    """

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutActionTokenizer"

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

    @property
    def config(self) -> LayoutActionConfig:
        """Return the paired LayoutAction config."""
        return self.tokenizer.config

    def __call__(
        self,
        *,
        condition_type: ConditionType | str = ConditionType.unconditional,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        batch_size: int = 1,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode a public generation condition.

        Args:
            condition_type: Canonical condition or supported release alias.
            bbox: Optional public boxes for completion prompts.
            labels: Optional labels for label/completion prompts.
            mask: Optional valid-element mask.
            num_elements: Optional element count or completion prefix length.
            box_format: Input box format.
            normalized: Whether boxes are normalized.
            canvas_size: Canvas size for pixel boxes.
            batch_size: Batch size for unconditional generation.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch encoding with prompt ids and optional forced token ids.

        Raises:
            NotImplementedError: If the condition is unsupported by LayoutAction.
            ValueError: If required condition payloads are missing.
        """
        if return_tensors != "pt":
            raise ValueError("LayoutActionProcessor only supports return_tensors='pt'")

        condition = normalize_condition_type(condition_type)
        if condition not in SUPPORTED_CONDITIONS:
            raise NotImplementedError(f"LayoutAction does not support {condition}.")

        if condition is ConditionType.unconditional:
            input_ids = torch.full(
                (int(batch_size), 1), self.config.bos_token_id, dtype=torch.long
            )
            return BatchEncoding(
                {
                    "input_ids": input_ids,
                    "attention_mask": torch.ones_like(input_ids),
                    "max_new_tokens": self.config.max_token_length,
                }
            )
        if labels is None:
            raise ValueError(f"{condition} generation requires labels")

        if bbox is None:
            label_tensor = torch.as_tensor(labels)
            if label_tensor.ndim == 1:
                label_tensor = label_tensor.unsqueeze(0)
            mask_tensor = (
                torch.ones_like(label_tensor, dtype=torch.bool)
                if mask is None
                else torch.as_tensor(mask, dtype=torch.bool)
            )
            if mask_tensor.ndim == 1:
                mask_tensor = mask_tensor.unsqueeze(0)
            bbox_tensor = torch.zeros(
                (*label_tensor.shape, 4),
                dtype=torch.float32,
                device=label_tensor.device,
            )
        else:
            bbox_tensor, label_tensor, mask_tensor = prepare_layout_tensors(
                bbox=bbox,
                labels=self._labels_to_ids(labels),
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
                clamp_converted_normalized=True,
            )
        full = self.tokenizer.encode_layout(
            bbox=bbox_tensor,
            labels=label_tensor.long(),
            mask=mask_tensor.bool(),
        )
        if condition is ConditionType.label:
            input_ids = full[:, :1]
            forced = torch.full(
                (full.size(0), self.config.max_token_length),
                -100,
                dtype=torch.long,
                device=full.device,
            )
            for step in range(
                0,
                self.config.max_elements * self.config.element_token_width,
                self.config.element_token_width,
            ):
                source_index = step + 1
                if source_index < full.size(1):
                    forced[:, step] = full[:, source_index]
            return BatchEncoding(
                {
                    "input_ids": input_ids,
                    "attention_mask": torch.ones_like(input_ids),
                    "forced_token_ids": forced,
                    "max_new_tokens": self.config.max_token_length,
                }
            )
        prefix_elements = self._prefix_elements(num_elements, mask_tensor)
        prefix_length = 1 + prefix_elements * self.config.element_token_width
        input_ids = full[:, :prefix_length]
        remaining = max(0, self.config.max_token_length + 1 - input_ids.size(1))
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "max_new_tokens": remaining,
            }
        )

    def post_process_layouts(
        self,
        sequences: Int[torch.Tensor, "batch tokens"],
        *,
        output_type: OutputType = "dataclass",
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | LayoutActionOutputDict:
        """Decode generated sequences to the common output schema."""
        decoded = self.tokenizer.decode_action_tokens(
            sequences.detach().cpu(),
            return_actions=return_intermediates,
        )
        intermediates = None
        if return_intermediates:
            intermediates = {"actions": decoded.get("actions")}
        output = LayoutGenerationOutput(
            bbox=cast(Float[torch.Tensor, "batch elements 4"], decoded["bbox"]),
            labels=cast(Int[torch.Tensor, "batch elements"], decoded["labels"]),
            mask=cast(Bool[torch.Tensor, "batch elements"], decoded["mask"]),
            id2label=dict(cast(dict[int, str], self.config.id2label)),
            sequences=sequences.detach().cpu(),
            intermediates=intermediates,
        )
        if output_type == "dict":
            return cast(LayoutActionOutputDict, dict(output))
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

    def save_pretrained(
        self,
        save_directory: str | PathLike[str],
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor and tokenizer metadata."""
        _ = (push_to_hub, kwargs)
        out_dir = Path(save_directory)
        out_dir.mkdir(parents=True, exist_ok=True)
        self.tokenizer.save_pretrained(out_dir)
        with (out_dir / PROCESSOR_CONFIG_FILE).open("w", encoding="utf-8") as f:
            json.dump({"processor_class": self.__class__.__name__}, f, indent=2)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutActionProcessor":
        """Load processor metadata from a checkpoint directory or Hub repo id."""
        tokenizer = LayoutActionTokenizer.from_pretrained(
            pretrained_model_name_or_path,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )
        return cls(tokenizer=tokenizer)

    def _labels_to_ids(
        self,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
    ) -> Int[torch.Tensor, "batch elements"]:
        if isinstance(labels, torch.Tensor):
            return labels.long()
        label_array = np.asarray(labels, dtype=object)
        if all(not isinstance(label, str) for label in label_array.flatten()):
            return torch.as_tensor(labels, dtype=torch.long)
        label2id = cast(dict[str, int], self.config.label2id)
        vectorized = np.vectorize(lambda label: label2id[str(label)])
        return torch.as_tensor(vectorized(label_array), dtype=torch.long)

    def _prefix_elements(
        self,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> int:
        if num_elements is None:
            valid_counts = mask.sum(dim=1)
            return int(valid_counts.min().item())
        if isinstance(num_elements, int):
            return int(num_elements)
        tensor = torch.as_tensor(num_elements)
        return int(tensor.min().item())

config property

config: LayoutActionConfig

Return the paired LayoutAction config.

__init__

__init__(tokenizer: LayoutActionTokenizer) -> None

Initialize the processor.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
62
63
64
65
def __init__(self, tokenizer: LayoutActionTokenizer) -> None:
    """Initialize the processor."""
    self.tokenizer = tokenizer
    self.chat_template = None

__call__

__call__(
    *,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode a public generation condition.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition or supported release alias.

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

Optional public boxes for completion prompts.

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

Optional labels for label/completion prompts.

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

Optional valid-element mask.

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

Optional element count or completion prefix length.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether boxes are normalized.

True
canvas_size tuple[int, int] | None

Canvas size for pixel boxes.

None
batch_size int

Batch size for unconditional generation.

1
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchEncoding

Batch encoding with prompt ids and optional forced token ids.

Raises:

Type Description
NotImplementedError

If the condition is unsupported by LayoutAction.

ValueError

If required condition payloads are missing.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
 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
def __call__(
    self,
    *,
    condition_type: ConditionType | str = ConditionType.unconditional,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode a public generation condition.

    Args:
        condition_type: Canonical condition or supported release alias.
        bbox: Optional public boxes for completion prompts.
        labels: Optional labels for label/completion prompts.
        mask: Optional valid-element mask.
        num_elements: Optional element count or completion prefix length.
        box_format: Input box format.
        normalized: Whether boxes are normalized.
        canvas_size: Canvas size for pixel boxes.
        batch_size: Batch size for unconditional generation.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch encoding with prompt ids and optional forced token ids.

    Raises:
        NotImplementedError: If the condition is unsupported by LayoutAction.
        ValueError: If required condition payloads are missing.
    """
    if return_tensors != "pt":
        raise ValueError("LayoutActionProcessor only supports return_tensors='pt'")

    condition = normalize_condition_type(condition_type)
    if condition not in SUPPORTED_CONDITIONS:
        raise NotImplementedError(f"LayoutAction does not support {condition}.")

    if condition is ConditionType.unconditional:
        input_ids = torch.full(
            (int(batch_size), 1), self.config.bos_token_id, dtype=torch.long
        )
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "max_new_tokens": self.config.max_token_length,
            }
        )
    if labels is None:
        raise ValueError(f"{condition} generation requires labels")

    if bbox is None:
        label_tensor = torch.as_tensor(labels)
        if label_tensor.ndim == 1:
            label_tensor = label_tensor.unsqueeze(0)
        mask_tensor = (
            torch.ones_like(label_tensor, dtype=torch.bool)
            if mask is None
            else torch.as_tensor(mask, dtype=torch.bool)
        )
        if mask_tensor.ndim == 1:
            mask_tensor = mask_tensor.unsqueeze(0)
        bbox_tensor = torch.zeros(
            (*label_tensor.shape, 4),
            dtype=torch.float32,
            device=label_tensor.device,
        )
    else:
        bbox_tensor, label_tensor, mask_tensor = prepare_layout_tensors(
            bbox=bbox,
            labels=self._labels_to_ids(labels),
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            clamp_converted_normalized=True,
        )
    full = self.tokenizer.encode_layout(
        bbox=bbox_tensor,
        labels=label_tensor.long(),
        mask=mask_tensor.bool(),
    )
    if condition is ConditionType.label:
        input_ids = full[:, :1]
        forced = torch.full(
            (full.size(0), self.config.max_token_length),
            -100,
            dtype=torch.long,
            device=full.device,
        )
        for step in range(
            0,
            self.config.max_elements * self.config.element_token_width,
            self.config.element_token_width,
        ):
            source_index = step + 1
            if source_index < full.size(1):
                forced[:, step] = full[:, source_index]
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "forced_token_ids": forced,
                "max_new_tokens": self.config.max_token_length,
            }
        )
    prefix_elements = self._prefix_elements(num_elements, mask_tensor)
    prefix_length = 1 + prefix_elements * self.config.element_token_width
    input_ids = full[:, :prefix_length]
    remaining = max(0, self.config.max_token_length + 1 - input_ids.size(1))
    return BatchEncoding(
        {
            "input_ids": input_ids,
            "attention_mask": torch.ones_like(input_ids),
            "max_new_tokens": remaining,
        }
    )

post_process_layouts

post_process_layouts(
    sequences: Int[Tensor, "batch tokens"],
    *,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | LayoutActionOutputDict

Decode generated sequences to the common output schema.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
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
def post_process_layouts(
    self,
    sequences: Int[torch.Tensor, "batch tokens"],
    *,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | LayoutActionOutputDict:
    """Decode generated sequences to the common output schema."""
    decoded = self.tokenizer.decode_action_tokens(
        sequences.detach().cpu(),
        return_actions=return_intermediates,
    )
    intermediates = None
    if return_intermediates:
        intermediates = {"actions": decoded.get("actions")}
    output = LayoutGenerationOutput(
        bbox=cast(Float[torch.Tensor, "batch elements 4"], decoded["bbox"]),
        labels=cast(Int[torch.Tensor, "batch elements"], decoded["labels"]),
        mask=cast(Bool[torch.Tensor, "batch elements"], decoded["mask"]),
        id2label=dict(cast(dict[int, str], self.config.id2label)),
        sequences=sequences.detach().cpu(),
        intermediates=intermediates,
    )
    if output_type == "dict":
        return cast(LayoutActionOutputDict, dict(output))
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")

save_pretrained

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

Save processor and tokenizer metadata.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
233
234
235
236
237
238
239
240
241
242
243
244
245
def save_pretrained(
    self,
    save_directory: str | PathLike[str],
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor and tokenizer metadata."""
    _ = (push_to_hub, kwargs)
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    self.tokenizer.save_pretrained(out_dir)
    with (out_dir / PROCESSOR_CONFIG_FILE).open("w", encoding="utf-8") as f:
        json.dump({"processor_class": self.__class__.__name__}, f, indent=2)

from_pretrained classmethod

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

Load processor metadata from a checkpoint directory or Hub repo id.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutActionProcessor":
    """Load processor metadata from a checkpoint directory or Hub repo id."""
    tokenizer = LayoutActionTokenizer.from_pretrained(
        pretrained_model_name_or_path,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )
    return cls(tokenizer=tokenizer)

LayoutActionTokenizer

Bases: PreTrainedTokenizer

PreTrainedTokenizer for LayoutAction's 13-token element grammar.

Parameters:

Name Type Description Default
config LayoutActionConfig | None

LayoutAction config carrying vocabulary metadata.

None
tokenizer_config_file str | None

Optional saved tokenizer metadata path.

None
kwargs str | int | float | bool | None

Standard tokenizer keyword arguments.

{}

Examples:

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

    Args:
        config: LayoutAction config carrying vocabulary metadata.
        tokenizer_config_file: Optional saved tokenizer metadata path.
        kwargs: Standard tokenizer keyword arguments.

    Examples:
        >>> tokenizer = LayoutActionTokenizer(LayoutActionConfig(max_elements=2))
        >>> tokenizer.bos_token_id == tokenizer.config.bos_token_id
        True
    """

    model_input_names = ["input_ids", "attention_mask"]
    vocab_files_names = {"tokenizer_config_file": TOKENIZER_CONFIG_FILE}

    def __init__(
        self,
        config: LayoutActionConfig | None = None,
        tokenizer_config_file: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize synthetic token strings."""
        if config is None and tokenizer_config_file is not None:
            with Path(tokenizer_config_file).open(encoding="utf-8") as f:
                config = LayoutActionConfig(**json.load(f)["config"])
        if config is None:
            raise ValueError("LayoutActionTokenizer requires an explicit config")

        self.config = config
        self._token2id = self._build_vocab()
        self._id2token = {idx: token for token, idx in self._token2id.items()}
        kwargs.setdefault("bos_token", "[BOS]")
        kwargs.setdefault("eos_token", "[EOS]")
        kwargs.setdefault("pad_token", "[PAD]")
        kwargs.setdefault("unk_token", "[UNK]")
        kwargs.setdefault("model_max_length", self.config.max_token_length + 1)
        kwargs.setdefault("clean_up_tokenization_spaces", False)
        super().__init__(**kwargs)

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

    def _build_vocab(self) -> dict[str, int]:
        vocab = {f"value:{idx}": idx for idx in range(self.config.size)}
        vocab["[NO_VALUE]"] = self.config.no_value_token_id
        id2label = cast(dict[int, str], self.config.id2label)
        for label_id, label in id2label.items():
            vocab[f"label:{label_id}:{label}"] = self.config.label_token_id(
                int(label_id)
            )
        vocab["[COPY]"] = self.config.copy_token_id
        vocab["[MARGIN]"] = self.config.margin_token_id
        vocab["[GENERATE]"] = self.config.generate_token_id
        vocab["[NO_OBJ]"] = self.config.no_obj_token_id
        for idx in range(1, self.config.max_elements + 1):
            vocab[f"obj:{idx}"] = self.config.object_token_id(idx)
        vocab["[BOS]"] = self.config.bos_token_id
        vocab["[EOS]"] = self.config.eos_token_id
        vocab["[PAD]"] = self.config.pad_token_id
        vocab["[UNK]"] = self.config.pad_token_id
        return vocab

    def get_vocab(self) -> dict[str, int]:
        """Return synthetic token strings mapped to ids."""
        return dict(self._token2id)

    def _tokenize(
        self, text: str, **kwargs: str | int | float | bool | None
    ) -> list[str]:
        _ = kwargs
        return text.strip().split()

    def _convert_token_to_id(self, token: str) -> int:
        return self._token2id.get(token, self.config.pad_token_id)

    def _convert_id_to_token(self, index: int) -> str:
        return self._id2token.get(int(index), "[UNK]")

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

    def save_vocabulary(
        self, save_directory: str | PathLike[str], filename_prefix: str | None = None
    ) -> tuple[str, ...]:
        """Save tokenizer metadata."""
        out_dir = Path(save_directory)
        out_dir.mkdir(parents=True, exist_ok=True)
        name = (
            TOKENIZER_CONFIG_FILE
            if filename_prefix is None
            else f"{filename_prefix}-{TOKENIZER_CONFIG_FILE}"
        )
        path = out_dir / name
        with path.open("w", encoding="utf-8") as f:
            json.dump({"config": self.config.to_dict()}, f, indent=2, sort_keys=True)
        return (str(path),)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        *inputs: str,
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutActionTokenizer":
        """Load tokenizer metadata through the standard Transformers resolver.

        Args:
            pretrained_model_name_or_path: Local tokenizer directory or Hub repo id.
            inputs: Reserved tokenizer inputs.
            cache_dir: Cache directory for Hub-backed files.
            force_download: Whether to refresh cached files.
            local_files_only: Whether to disable network resolution.
            token: Hugging Face token.
            revision: Hub revision.
            kwargs: Standard tokenizer keyword arguments.

        Returns:
            Loaded LayoutAction tokenizer.
        """
        _ = inputs
        subfolder = str(kwargs.pop("subfolder", ""))
        metadata = cached_file(
            pretrained_model_name_or_path,
            TOKENIZER_CONFIG_FILE,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            subfolder=subfolder,
        )
        if metadata is None:
            raise FileNotFoundError(
                f"Could not resolve {TOKENIZER_CONFIG_FILE} from "
                f"{pretrained_model_name_or_path!s}"
            )

        with Path(metadata).open(encoding="utf-8") as f:
            config = LayoutActionConfig(**json.load(f)["config"])
        return cls(config=config)

    def quantize_bbox(
        self, bbox: Float[torch.Tensor, "... 4"]
    ) -> Int[torch.Tensor, "... 4"]:
        """Quantize normalized center ``xywh`` boxes with checkpoint binning."""
        return (
            bbox.clamp(0.0, 1.0)
            .mul(self.config.size - 1)
            .round()
            .long()
            .clamp(0, self.config.size - 1)
        )

    def continuize_bbox(
        self, quantized_bbox: Int[torch.Tensor, "... 4"]
    ) -> Float[torch.Tensor, "... 4"]:
        """Decode quantized boxes to normalized center ``xywh`` values."""
        return quantized_bbox.float().clamp(0, self.config.size - 1) / (
            self.config.size - 1
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Encode public normalized layouts to padded action-token sequences.

        Args:
            bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
            labels: Dataset-local labels shaped ``(B, E)``.
            mask: Valid-element mask shaped ``(B, E)``.

        Returns:
            Token ids shaped ``(B, max_token_length + 1)``.
        """
        quantized_bbox = self.quantize_bbox(bbox)
        return self.encode_action_layout(
            quantized_bbox=quantized_bbox,
            labels=labels.long(),
            mask=mask.bool(),
        )

    def encode_action_layout(
        self,
        *,
        quantized_bbox: Int[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Encode already quantized boxes to action tokens."""
        batch = quantized_bbox.shape[0]
        sequences = torch.full(
            (batch, self.config.max_token_length + 1),
            self.config.pad_token_id,
            dtype=torch.long,
            device=quantized_bbox.device,
        )
        sequences[:, 0] = self.config.bos_token_id

        for batch_idx in range(batch):
            cursor = 1
            valid = torch.nonzero(mask[batch_idx], as_tuple=False).flatten()
            encoded_boxes: list[Int[torch.Tensor, "4"]] = []

            for elem_idx in valid[: self.config.max_elements]:
                label_id = int(labels[batch_idx, elem_idx].item())
                qbox = quantized_bbox[batch_idx, elem_idx].long()
                sequences[batch_idx, cursor] = self.config.label_token_id(label_id)
                cursor += 1
                triples = self._encode_action_triples(qbox, encoded_boxes)
                for geo_idx, (option_id, object_id, value_id) in enumerate(triples):
                    _ = geo_idx
                    sequences[batch_idx, cursor] = option_id
                    sequences[batch_idx, cursor + 1] = object_id
                    sequences[batch_idx, cursor + 2] = value_id
                    cursor += 3
                encoded_boxes.append(qbox.detach().clone())

            sequences[batch_idx, cursor] = self.config.eos_token_id
        return sequences

    def _encode_action_triples(
        self, qbox: Int[torch.Tensor, "4"], previous_boxes: list[Int[torch.Tensor, "4"]]
    ) -> list[tuple[int, int, int]]:
        """Encode one quantized box with checkpoint copy/margin/generate precedence."""
        if not previous_boxes:
            return [
                (
                    self.config.generate_token_id,
                    self.config.no_obj_token_id,
                    int(qbox[geo_idx].item()),
                )
                for geo_idx in range(4)
            ]
        previous = torch.stack(previous_boxes).to(device=qbox.device, dtype=torch.long)
        current = qbox.to(device=previous.device, dtype=torch.long)
        copy_label = previous.eq(current.unsqueeze(0))
        copy_choice = copy_label.any(dim=0)
        margin_label = torch.zeros(
            (previous.size(0), 2), dtype=torch.bool, device=previous.device
        )
        margin_label[:, 0] = previous[:, 1].eq(current[1])
        margin_label[:, 1] = previous[:, 0].eq(current[0])
        margin_value = (
            current[:2].float().unsqueeze(0)
            - previous[:, :2].float()
            - 0.5 * previous[:, 2:].float()
            - 0.5 * current[2:].float().unsqueeze(0)
        )
        margin_label &= margin_value.ge(0)
        margin_label_x4 = torch.cat(
            [margin_label, torch.zeros_like(margin_label)], dim=1
        )
        margin_choice = margin_label_x4.any(dim=0) & ~copy_choice
        generate_choice = ~(copy_choice | margin_choice)
        triples: list[tuple[int, int, int]] = []
        for geo_idx in range(4):
            if bool(copy_choice[geo_idx].item()):
                ref = self._latest_back_reference(copy_label[:, geo_idx])
                triples.append(
                    (
                        self.config.copy_token_id,
                        self.config.object_token_id(ref),
                        self.config.no_value_token_id,
                    )
                )
            elif bool(margin_choice[geo_idx].item()):
                ref = self._latest_back_reference(margin_label_x4[:, geo_idx])
                value = int(round(float(margin_value[-ref, geo_idx].item())))
                triples.append(
                    (
                        self.config.margin_token_id,
                        self.config.object_token_id(ref),
                        max(0, min(self.config.size - 1, value)),
                    )
                )
            elif bool(generate_choice[geo_idx].item()):
                triples.append(
                    (
                        self.config.generate_token_id,
                        self.config.no_obj_token_id,
                        int(current[geo_idx].item()),
                    )
                )
            else:
                raise ValueError("LayoutAction action selection produced no option")

        return triples

    def _latest_back_reference(self, hits: Bool[torch.Tensor, "elements"]) -> int:
        hit_indices = torch.nonzero(hits, as_tuple=False).flatten()
        if hit_indices.numel() == 0:
            raise ValueError("Expected at least one back-reference hit")

        return int(hits.numel() - hit_indices[-1].item())

    def decode_layout(
        self, input_ids: Int[torch.Tensor, "batch_or_tokens ..."]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Decode action-token sequences to public layout tensors."""
        return cast(
            dict[str, Shaped[torch.Tensor, "..."]],
            self.decode_action_tokens(input_ids, return_actions=False),
        )

    def decode_action_tokens(
        self,
        input_ids: Int[torch.Tensor, "batch_or_tokens ..."],
        *,
        return_actions: bool = False,
    ) -> dict[
        str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
    ]:
        """Decode action tokens and optionally return raw action details."""
        ids = input_ids.long()
        if ids.ndim == 1:
            ids = ids.unsqueeze(0)
        batch = ids.shape[0]
        bbox = torch.zeros(batch, self.config.max_elements, 4, dtype=torch.float32)
        labels = torch.zeros(batch, self.config.max_elements, dtype=torch.long)
        mask = torch.zeros(batch, self.config.max_elements, dtype=torch.bool)
        option = torch.full((batch, self.config.max_elements, 4), -1, dtype=torch.long)
        obj = torch.full_like(option, -1)
        value = torch.full_like(option, -1)
        for batch_idx in range(batch):
            tokens = self._trim_special_tokens(ids[batch_idx])
            usable = tokens[
                : (tokens.numel() // self.config.element_token_width)
                * self.config.element_token_width
            ]

            boxes: list[Int[torch.Tensor, "4"]] = []
            out_idx = 0
            for start in range(0, usable.numel(), self.config.element_token_width):
                if out_idx >= self.config.max_elements:
                    break
                element = usable[start : start + self.config.element_token_width]
                label_id = self.config.label_id_from_token(int(element[0]))
                if label_id is None:
                    continue
                qbox = torch.zeros(4, dtype=torch.long)
                valid = True
                deferred_margins: list[tuple[int, int, int]] = []

                for geo_idx in range(4):
                    triple = element[1 + geo_idx * 3 : 1 + (geo_idx + 1) * 3]
                    opt_id = int(triple[0])
                    obj_id = int(triple[1])
                    val_id = int(triple[2])
                    option[batch_idx, out_idx, geo_idx] = opt_id
                    obj[batch_idx, out_idx, geo_idx] = obj_id
                    value[batch_idx, out_idx, geo_idx] = val_id
                    if (
                        opt_id == self.config.generate_token_id
                        and 0 <= val_id < self.config.size
                    ):
                        qbox[geo_idx] = val_id
                    elif opt_id == self.config.copy_token_id:
                        ref = self.config.back_reference_from_token(obj_id)
                        if ref is None or ref > len(boxes):
                            valid = False
                            break
                        qbox[geo_idx] = boxes[-ref][geo_idx].long()
                    elif opt_id == self.config.margin_token_id and geo_idx < 2:
                        ref = self.config.back_reference_from_token(obj_id)
                        if (
                            ref is None
                            or ref > len(boxes)
                            or not (0 <= val_id < self.config.size)
                        ):
                            valid = False
                            break
                        deferred_margins.append((geo_idx, ref, val_id))
                    else:
                        valid = False
                        break
                if not valid:
                    continue

                for geo_idx, ref, val_id in deferred_margins:
                    base = self.continuize_bbox(boxes[-ref].unsqueeze(0))[0]
                    cur = self.continuize_bbox(qbox.unsqueeze(0))[0]
                    margin = float(val_id) / (self.config.size - 1)
                    coord = (
                        base[geo_idx]
                        + 0.5 * base[geo_idx + 2]
                        + 0.5 * cur[geo_idx + 2]
                        + margin
                    )
                    qbox[geo_idx] = int(
                        round(float(coord.item()) * (self.config.size - 1))
                    )

                boxes.append(qbox.clone())
                bbox[batch_idx, out_idx] = self.continuize_bbox(qbox)
                labels[batch_idx, out_idx] = label_id
                mask[batch_idx, out_idx] = True
                out_idx += 1
        result: dict[
            str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
        ] = {
            "bbox": bbox.clamp(0.0, 1.0),
            "labels": labels,
            "mask": mask,
        }
        if return_actions:
            result["actions"] = {"option": option, "object": obj, "value": value}
        return result

    def _trim_special_tokens(
        self, input_ids: Int[torch.Tensor, "tokens"]
    ) -> Int[torch.Tensor, "tokens"]:
        tokens = input_ids.detach().cpu()
        bos = torch.nonzero(tokens == self.config.bos_token_id, as_tuple=False)
        if bos.numel() > 0:
            tokens = tokens[int(bos[0].item()) + 1 :]
        eos = torch.nonzero(tokens == self.config.eos_token_id, as_tuple=False)
        if eos.numel() > 0:
            tokens = tokens[: int(eos[0].item())]
        return tokens[tokens != self.config.pad_token_id]

vocab_size property

vocab_size: int

Return the LayoutAction vocabulary size.

__init__

__init__(
    config: LayoutActionConfig | None = None,
    tokenizer_config_file: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize synthetic token strings.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    config: LayoutActionConfig | None = None,
    tokenizer_config_file: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize synthetic token strings."""
    if config is None and tokenizer_config_file is not None:
        with Path(tokenizer_config_file).open(encoding="utf-8") as f:
            config = LayoutActionConfig(**json.load(f)["config"])
    if config is None:
        raise ValueError("LayoutActionTokenizer requires an explicit config")

    self.config = config
    self._token2id = self._build_vocab()
    self._id2token = {idx: token for token, idx in self._token2id.items()}
    kwargs.setdefault("bos_token", "[BOS]")
    kwargs.setdefault("eos_token", "[EOS]")
    kwargs.setdefault("pad_token", "[PAD]")
    kwargs.setdefault("unk_token", "[UNK]")
    kwargs.setdefault("model_max_length", self.config.max_token_length + 1)
    kwargs.setdefault("clean_up_tokenization_spaces", False)
    super().__init__(**kwargs)

get_vocab

get_vocab() -> dict[str, int]

Return synthetic token strings mapped to ids.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
94
95
96
def get_vocab(self) -> dict[str, int]:
    """Return synthetic token strings mapped to ids."""
    return dict(self._token2id)

convert_tokens_to_string

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

Join synthetic layout tokens.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
110
111
112
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join synthetic layout tokens."""
    return " ".join(tokens)

save_vocabulary

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

Save tokenizer metadata.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def save_vocabulary(
    self, save_directory: str | PathLike[str], filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save tokenizer metadata."""
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    name = (
        TOKENIZER_CONFIG_FILE
        if filename_prefix is None
        else f"{filename_prefix}-{TOKENIZER_CONFIG_FILE}"
    )
    path = out_dir / name
    with path.open("w", encoding="utf-8") as f:
        json.dump({"config": self.config.to_dict()}, f, indent=2, sort_keys=True)
    return (str(path),)

from_pretrained classmethod

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

Load tokenizer metadata through the standard Transformers resolver.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | PathLike[str]

Local tokenizer directory or Hub repo id.

required
inputs str

Reserved tokenizer inputs.

()
cache_dir str | PathLike[str] | None

Cache directory for Hub-backed files.

None
force_download bool

Whether to refresh cached files.

False
local_files_only bool

Whether to disable network resolution.

False
token str | bool | None

Hugging Face token.

None
revision str

Hub revision.

'main'
kwargs str | int | float | bool | None

Standard tokenizer keyword arguments.

{}

Returns:

Type Description
'LayoutActionTokenizer'

Loaded LayoutAction tokenizer.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    *inputs: str,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutActionTokenizer":
    """Load tokenizer metadata through the standard Transformers resolver.

    Args:
        pretrained_model_name_or_path: Local tokenizer directory or Hub repo id.
        inputs: Reserved tokenizer inputs.
        cache_dir: Cache directory for Hub-backed files.
        force_download: Whether to refresh cached files.
        local_files_only: Whether to disable network resolution.
        token: Hugging Face token.
        revision: Hub revision.
        kwargs: Standard tokenizer keyword arguments.

    Returns:
        Loaded LayoutAction tokenizer.
    """
    _ = inputs
    subfolder = str(kwargs.pop("subfolder", ""))
    metadata = cached_file(
        pretrained_model_name_or_path,
        TOKENIZER_CONFIG_FILE,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        subfolder=subfolder,
    )
    if metadata is None:
        raise FileNotFoundError(
            f"Could not resolve {TOKENIZER_CONFIG_FILE} from "
            f"{pretrained_model_name_or_path!s}"
        )

    with Path(metadata).open(encoding="utf-8") as f:
        config = LayoutActionConfig(**json.load(f)["config"])
    return cls(config=config)

quantize_bbox

quantize_bbox(
    bbox: Float[Tensor, "... 4"],
) -> Int[torch.Tensor, "... 4"]

Quantize normalized center xywh boxes with checkpoint binning.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
179
180
181
182
183
184
185
186
187
188
189
def quantize_bbox(
    self, bbox: Float[torch.Tensor, "... 4"]
) -> Int[torch.Tensor, "... 4"]:
    """Quantize normalized center ``xywh`` boxes with checkpoint binning."""
    return (
        bbox.clamp(0.0, 1.0)
        .mul(self.config.size - 1)
        .round()
        .long()
        .clamp(0, self.config.size - 1)
    )

continuize_bbox

continuize_bbox(
    quantized_bbox: Int[Tensor, "... 4"],
) -> Float[torch.Tensor, "... 4"]

Decode quantized boxes to normalized center xywh values.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
191
192
193
194
195
196
197
def continuize_bbox(
    self, quantized_bbox: Int[torch.Tensor, "... 4"]
) -> Float[torch.Tensor, "... 4"]:
    """Decode quantized boxes to normalized center ``xywh`` values."""
    return quantized_bbox.float().clamp(0, self.config.size - 1) / (
        self.config.size - 1
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]

Encode public normalized layouts to padded action-token sequences.

Parameters:

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

Normalized center xywh boxes shaped (B, E, 4).

required
labels Int[Tensor, 'batch elements']

Dataset-local labels shaped (B, E).

required
mask Bool[Tensor, 'batch elements']

Valid-element mask shaped (B, E).

required

Returns:

Type Description
Int[Tensor, 'batch tokens']

Token ids shaped (B, max_token_length + 1).

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Encode public normalized layouts to padded action-token sequences.

    Args:
        bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
        labels: Dataset-local labels shaped ``(B, E)``.
        mask: Valid-element mask shaped ``(B, E)``.

    Returns:
        Token ids shaped ``(B, max_token_length + 1)``.
    """
    quantized_bbox = self.quantize_bbox(bbox)
    return self.encode_action_layout(
        quantized_bbox=quantized_bbox,
        labels=labels.long(),
        mask=mask.bool(),
    )

encode_action_layout

encode_action_layout(
    *,
    quantized_bbox: Int[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]

Encode already quantized boxes to action tokens.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
def encode_action_layout(
    self,
    *,
    quantized_bbox: Int[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Encode already quantized boxes to action tokens."""
    batch = quantized_bbox.shape[0]
    sequences = torch.full(
        (batch, self.config.max_token_length + 1),
        self.config.pad_token_id,
        dtype=torch.long,
        device=quantized_bbox.device,
    )
    sequences[:, 0] = self.config.bos_token_id

    for batch_idx in range(batch):
        cursor = 1
        valid = torch.nonzero(mask[batch_idx], as_tuple=False).flatten()
        encoded_boxes: list[Int[torch.Tensor, "4"]] = []

        for elem_idx in valid[: self.config.max_elements]:
            label_id = int(labels[batch_idx, elem_idx].item())
            qbox = quantized_bbox[batch_idx, elem_idx].long()
            sequences[batch_idx, cursor] = self.config.label_token_id(label_id)
            cursor += 1
            triples = self._encode_action_triples(qbox, encoded_boxes)
            for geo_idx, (option_id, object_id, value_id) in enumerate(triples):
                _ = geo_idx
                sequences[batch_idx, cursor] = option_id
                sequences[batch_idx, cursor + 1] = object_id
                sequences[batch_idx, cursor + 2] = value_id
                cursor += 3
            encoded_boxes.append(qbox.detach().clone())

        sequences[batch_idx, cursor] = self.config.eos_token_id
    return sequences

decode_layout

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

Decode action-token sequences to public layout tensors.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
337
338
339
340
341
342
343
344
def decode_layout(
    self, input_ids: Int[torch.Tensor, "batch_or_tokens ..."]
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Decode action-token sequences to public layout tensors."""
    return cast(
        dict[str, Shaped[torch.Tensor, "..."]],
        self.decode_action_tokens(input_ids, return_actions=False),
    )

decode_action_tokens

decode_action_tokens(
    input_ids: Int[Tensor, "batch_or_tokens ..."],
    *,
    return_actions: bool = False,
) -> dict[
    str,
    Shaped[torch.Tensor, "..."]
    | dict[str, Shaped[torch.Tensor, "..."]],
]

Decode action tokens and optionally return raw action details.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
def decode_action_tokens(
    self,
    input_ids: Int[torch.Tensor, "batch_or_tokens ..."],
    *,
    return_actions: bool = False,
) -> dict[
    str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
]:
    """Decode action tokens and optionally return raw action details."""
    ids = input_ids.long()
    if ids.ndim == 1:
        ids = ids.unsqueeze(0)
    batch = ids.shape[0]
    bbox = torch.zeros(batch, self.config.max_elements, 4, dtype=torch.float32)
    labels = torch.zeros(batch, self.config.max_elements, dtype=torch.long)
    mask = torch.zeros(batch, self.config.max_elements, dtype=torch.bool)
    option = torch.full((batch, self.config.max_elements, 4), -1, dtype=torch.long)
    obj = torch.full_like(option, -1)
    value = torch.full_like(option, -1)
    for batch_idx in range(batch):
        tokens = self._trim_special_tokens(ids[batch_idx])
        usable = tokens[
            : (tokens.numel() // self.config.element_token_width)
            * self.config.element_token_width
        ]

        boxes: list[Int[torch.Tensor, "4"]] = []
        out_idx = 0
        for start in range(0, usable.numel(), self.config.element_token_width):
            if out_idx >= self.config.max_elements:
                break
            element = usable[start : start + self.config.element_token_width]
            label_id = self.config.label_id_from_token(int(element[0]))
            if label_id is None:
                continue
            qbox = torch.zeros(4, dtype=torch.long)
            valid = True
            deferred_margins: list[tuple[int, int, int]] = []

            for geo_idx in range(4):
                triple = element[1 + geo_idx * 3 : 1 + (geo_idx + 1) * 3]
                opt_id = int(triple[0])
                obj_id = int(triple[1])
                val_id = int(triple[2])
                option[batch_idx, out_idx, geo_idx] = opt_id
                obj[batch_idx, out_idx, geo_idx] = obj_id
                value[batch_idx, out_idx, geo_idx] = val_id
                if (
                    opt_id == self.config.generate_token_id
                    and 0 <= val_id < self.config.size
                ):
                    qbox[geo_idx] = val_id
                elif opt_id == self.config.copy_token_id:
                    ref = self.config.back_reference_from_token(obj_id)
                    if ref is None or ref > len(boxes):
                        valid = False
                        break
                    qbox[geo_idx] = boxes[-ref][geo_idx].long()
                elif opt_id == self.config.margin_token_id and geo_idx < 2:
                    ref = self.config.back_reference_from_token(obj_id)
                    if (
                        ref is None
                        or ref > len(boxes)
                        or not (0 <= val_id < self.config.size)
                    ):
                        valid = False
                        break
                    deferred_margins.append((geo_idx, ref, val_id))
                else:
                    valid = False
                    break
            if not valid:
                continue

            for geo_idx, ref, val_id in deferred_margins:
                base = self.continuize_bbox(boxes[-ref].unsqueeze(0))[0]
                cur = self.continuize_bbox(qbox.unsqueeze(0))[0]
                margin = float(val_id) / (self.config.size - 1)
                coord = (
                    base[geo_idx]
                    + 0.5 * base[geo_idx + 2]
                    + 0.5 * cur[geo_idx + 2]
                    + margin
                )
                qbox[geo_idx] = int(
                    round(float(coord.item()) * (self.config.size - 1))
                )

            boxes.append(qbox.clone())
            bbox[batch_idx, out_idx] = self.continuize_bbox(qbox)
            labels[batch_idx, out_idx] = label_id
            mask[batch_idx, out_idx] = True
            out_idx += 1
    result: dict[
        str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
    ] = {
        "bbox": bbox.clamp(0.0, 1.0),
        "labels": labels,
        "mask": mask,
    }
    if return_actions:
        result["actions"] = {"option": option, "object": obj, "value": value}
    return result

convert_layout_action_checkpoint

convert_layout_action_checkpoint(
    *,
    checkpoint: str | Path,
    output_dir: str | Path,
    config: LayoutActionConfig,
    strict: bool = True,
) -> LayoutActionConversionReport

Convert a raw vendor .pth checkpoint to HF-style files.

Parameters:

Name Type Description Default
checkpoint str | Path

Raw PyTorch state-dict path.

required
output_dir str | Path

Destination checkpoint directory.

required
config LayoutActionConfig

LayoutAction config built from dataset metadata.

required
strict bool

Whether model loading is strict.

True

Returns:

Type Description
LayoutActionConversionReport

Conversion report dictionary.

Source code in models/layout-action/src/layout_action/conversion.py
 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
def convert_layout_action_checkpoint(
    *,
    checkpoint: str | Path,
    output_dir: str | Path,
    config: LayoutActionConfig,
    strict: bool = True,
) -> LayoutActionConversionReport:
    """Convert a raw vendor ``.pth`` checkpoint to HF-style files.

    Args:
        checkpoint: Raw PyTorch state-dict path.
        output_dir: Destination checkpoint directory.
        config: LayoutAction config built from dataset metadata.
        strict: Whether model loading is strict.

    Returns:
        Conversion report dictionary.
    """
    checkpoint_path = Path(checkpoint)
    model = LayoutActionForCausalLM(config)
    raw = torch.load(checkpoint_path, map_location="cpu")
    if not isinstance(raw, dict):
        raise TypeError("LayoutAction checkpoint must be a state-dict mapping")

    remapped, report = remap_state_dict(raw, model)
    missing, unexpected = model.load_state_dict(remapped, strict=strict)
    out_dir = Path(output_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    processor = LayoutActionProcessor(LayoutActionTokenizer(config))
    model.save_pretrained(out_dir)
    processor.save_pretrained(out_dir)
    checkpoint_sha256 = sha256_file(checkpoint_path)
    config.original_asset_manifest = {
        **config.original_asset_manifest,
        "checkpoint": str(checkpoint_path),
        "checkpoint_sha256": checkpoint_sha256,
    }
    config.save_pretrained(out_dir)
    conversion_report = LayoutActionConversionReport(
        checkpoint=str(checkpoint_path),
        checkpoint_sha256=checkpoint_sha256,
        config=cast(
            dict[
                str,
                str
                | int
                | float
                | bool
                | dict[int, str]
                | dict[str, int]
                | dict[str, str | int | list[str] | dict[str, str | int]]
                | None,
            ],
            config.to_dict(),
        ),
        keys=[cast(StateDictKeyReportDict, asdict(row)) for row in report],
        missing_keys=list(missing),
        unexpected_keys=list(unexpected),
    )
    with (out_dir / "conversion_report.json").open("w", encoding="utf-8") as f:
        json.dump(conversion_report, f, indent=2, sort_keys=True)
    return conversion_report

remap_layout_action_key

remap_layout_action_key(key: str) -> str

Map a vendor LayoutAction state-dict key to this package.

Source code in models/layout-action/src/layout_action/conversion.py
60
61
62
def remap_layout_action_key(key: str) -> str:
    """Map a vendor LayoutAction state-dict key to this package."""
    return key

remap_state_dict

remap_state_dict(
    state_dict: dict[str, Shaped[Tensor, "..."]],
    model: LayoutActionForCausalLM,
) -> tuple[
    dict[str, Shaped[torch.Tensor, "..."]],
    list[StateDictKeyReport],
]

Remap and report checkpoint key coverage.

Source code in models/layout-action/src/layout_action/conversion.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def remap_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
    model: LayoutActionForCausalLM,
) -> tuple[dict[str, Shaped[torch.Tensor, "..."]], list[StateDictKeyReport]]:
    """Remap and report checkpoint key coverage."""
    target_keys = set(model.state_dict())
    remapped: dict[str, Shaped[torch.Tensor, "..."]] = {}
    report: list[StateDictKeyReport] = []
    for source_key, value in state_dict.items():
        target_key = remap_layout_action_key(source_key)
        loaded = target_key in target_keys
        if loaded:
            remapped[target_key] = value
        report.append(
            StateDictKeyReport(
                source_key=source_key,
                target_key=target_key,
                source_shape=tuple(value.shape),
                loaded=loaded,
            )
        )
    return remapped, report

sample_action_tokens

sample_action_tokens(
    model: ActionTokenModel,
    input_ids: Int[Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    sampling: LayoutActionSamplingConfig,
    forced_token_ids: Int[Tensor, "batch new_tokens"]
    | None = None,
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]

Autoregressively sample LayoutAction token ids.

Parameters:

Name Type Description Default
model ActionTokenModel

Token model returning logits.

required
input_ids Int[Tensor, 'batch sequence']

Prompt ids shaped (batch, prompt).

required
max_new_tokens int

Number of new tokens to append.

required
sampling LayoutActionSamplingConfig

Sampling parameters.

required
forced_token_ids Int[Tensor, 'batch new_tokens'] | None

Optional ids shaped (batch, max_new_tokens) with -100 for freely sampled positions.

None
generator Generator | None

Optional torch generator for multinomial sampling.

None

Returns:

Type Description
Int[Tensor, 'batch sequence']

Prompt plus sampled token ids.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
 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
@torch.no_grad()
def sample_action_tokens(
    model: ActionTokenModel,
    input_ids: Int[torch.Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    sampling: LayoutActionSamplingConfig,
    forced_token_ids: Int[torch.Tensor, "batch new_tokens"] | None = None,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]:
    """Autoregressively sample LayoutAction token ids.

    Args:
        model: Token model returning logits.
        input_ids: Prompt ids shaped ``(batch, prompt)``.
        max_new_tokens: Number of new tokens to append.
        sampling: Sampling parameters.
        forced_token_ids: Optional ids shaped ``(batch, max_new_tokens)`` with
            ``-100`` for freely sampled positions.
        generator: Optional torch generator for multinomial sampling.

    Returns:
        Prompt plus sampled token ids.
    """
    block_size = model.get_block_size()
    sequence = input_ids.long()
    for step in range(max_new_tokens):
        if forced_token_ids is not None:
            forced = forced_token_ids[:, step]
            if bool(torch.all(forced.ge(0))):
                sequence = torch.cat((sequence, forced.unsqueeze(1)), dim=1)
                continue
        context = (
            sequence if sequence.size(1) <= block_size else sequence[:, -block_size:]
        )
        raw_output = model(context)
        logits = raw_output.logits
        next_logits = logits[:, -1, :] / sampling.temperature
        if (
            sampling.mode is LayoutActionSamplingMode.top_k
            and sampling.top_k is not None
        ):
            next_logits = top_k_logits(next_logits, sampling.top_k)
        probs = F.softmax(next_logits, dim=-1)
        if sampling.mode is LayoutActionSamplingMode.greedy:
            _, next_id = torch.topk(probs, k=1, dim=-1)
        else:
            next_id = torch.multinomial(probs, num_samples=1, generator=generator)
        if forced_token_ids is not None:
            forced = forced_token_ids[:, step].unsqueeze(1)
            next_id = torch.where(forced.ge(0), forced, next_id)
        sequence = torch.cat((sequence, next_id), dim=1)
    return sequence

top_k_logits

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

Mask logits outside the top k values exactly like the reference helper.

Parameters:

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

Logits shaped (batch, vocab).

required
k int

Number of top logits to keep.

required

Returns:

Type Description
Float[Tensor, 'batch vocab']

Logits with non-top-k entries set to negative infinity.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def top_k_logits(
    logits: Float[torch.Tensor, "batch vocab"], k: int
) -> Float[torch.Tensor, "batch vocab"]:
    """Mask logits outside the top ``k`` values exactly like the reference helper.

    Args:
        logits: Logits shaped ``(batch, vocab)``.
        k: Number of top logits to keep.

    Returns:
        Logits with non-top-k entries set to negative infinity.
    """
    values, _ = torch.topk(logits, k)
    out = logits.clone()
    out[out < values[:, [-1]]] = -float("Inf")
    return out

configuration_layout_action

Configuration for converted LayoutAction checkpoints.

AssetManifestFile

Bases: TypedDict

One inventoried original asset.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
20
21
22
23
24
class AssetManifestFile(TypedDict):
    """One inventoried original asset."""

    size: int
    sha256: str

LayoutActionAssetManifest

Bases: TypedDict

Original LayoutAction asset manifest.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
27
28
29
30
31
32
33
34
35
36
class LayoutActionAssetManifest(TypedDict, total=False):
    """Original LayoutAction asset manifest."""

    source: str
    google_drive_folder_id: str
    files: dict[str, AssetManifestFile]
    missing_required: list[str]
    missing_optional: list[str]
    checkpoint: str
    checkpoint_sha256: str

LayoutActionSamplingMode

Bases: StrEnum

Supported token sampling modes.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
39
40
41
42
43
44
class LayoutActionSamplingMode(StrEnum):
    """Supported token sampling modes."""

    greedy = auto()
    multinomial = auto()
    top_k = auto()

LayoutActionConfig

Bases: PretrainedConfig

Architecture and tokenizer metadata for LayoutAction checkpoints.

Parameters:

Name Type Description Default
dataset_name str

Dataset slug. rico and layout_action_rico13 map to the released RICO13 label order.

'rico13'
id2label Mapping[int, str] | Mapping[str, str] | None

Dataset-local label mapping.

None
precision int

Coordinate precision; checkpoint defaults to 8 bits.

8
max_elements int | None

Maximum number of layout elements.

None
block_size int | None

GPT context length. Defaults to released max token length.

None
vocab_size int | None

Token vocabulary size. Defaults to the reference formula.

None
n_layer int

Number of GPT blocks.

6
n_head int

Attention heads.

8
n_embd int

Hidden size.

512
embd_pdrop float

Embedding dropout.

0.1
resid_pdrop float

Residual dropout.

0.1
attn_pdrop float

Attention dropout.

0.1
default_sampling LayoutActionSamplingMode | str

Default pipeline sampling mode.

top_k
default_top_k int

Default top-k value.

5
default_temperature float

Default sampling temperature.

1.0
original_dataset_name str | None

Original dataset name.

None
original_asset_manifest Mapping[str, str | int | list[str] | dict[str, AssetManifestFile]] | None

Optional asset manifest.

None
kwargs str | int | float | bool | None

Additional PretrainedConfig fields.

{}

Examples:

>>> config = LayoutActionConfig(dataset_name="publaynet")
>>> config.bos_token_id == config.vocab_size - 3
True
Source code in models/layout-action/src/layout_action/configuration_layout_action.py
 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
class LayoutActionConfig(PretrainedConfig):
    """Architecture and tokenizer metadata for LayoutAction checkpoints.

    Args:
        dataset_name: Dataset slug. ``rico`` and ``layout_action_rico13`` map to
            the released RICO13 label order.
        id2label: Dataset-local label mapping.
        precision: Coordinate precision; checkpoint defaults to 8 bits.
        max_elements: Maximum number of layout elements.
        block_size: GPT context length. Defaults to released max token length.
        vocab_size: Token vocabulary size. Defaults to the reference formula.
        n_layer: Number of GPT blocks.
        n_head: Attention heads.
        n_embd: Hidden size.
        embd_pdrop: Embedding dropout.
        resid_pdrop: Residual dropout.
        attn_pdrop: Attention dropout.
        default_sampling: Default pipeline sampling mode.
        default_top_k: Default top-k value.
        default_temperature: Default sampling temperature.
        original_dataset_name: Original dataset name.
        original_asset_manifest: Optional asset manifest.
        kwargs: Additional ``PretrainedConfig`` fields.

    Examples:
        >>> config = LayoutActionConfig(dataset_name="publaynet")
        >>> config.bos_token_id == config.vocab_size - 3
        True
    """

    model_type = "layout-action"

    def __init__(
        self,
        *,
        dataset_name: str = "rico13",
        id2label: Mapping[int, str] | Mapping[str, str] | None = None,
        precision: int = 8,
        max_elements: int | None = None,
        block_size: int | None = None,
        vocab_size: int | None = None,
        n_layer: int = 6,
        n_head: int = 8,
        n_embd: int = 512,
        embd_pdrop: float = 0.1,
        resid_pdrop: float = 0.1,
        attn_pdrop: float = 0.1,
        default_sampling: LayoutActionSamplingMode
        | str = LayoutActionSamplingMode.top_k,
        default_top_k: int = 5,
        default_temperature: float = 1.0,
        original_dataset_name: str | None = None,
        original_asset_manifest: Mapping[
            str, str | int | list[str] | dict[str, AssetManifestFile]
        ]
        | None = None,
        model_type: str | None = None,
        transformers_version: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize LayoutAction metadata and derived token ids."""
        _ = (model_type, transformers_version)
        for derived_key in (
            "bos_token_id",
            "eos_token_id",
            "pad_token_id",
            "label2id",
            "size",
            "element_token_width",
            "max_token_length",
            "no_value_token_id",
            "label_token_offset",
            "copy_token_id",
            "margin_token_id",
            "generate_token_id",
            "no_obj_token_id",
        ):
            kwargs.pop(derived_key, None)
        _ = kwargs
        super().__init__()
        dataset = normalize_vendor_dataset_name(dataset_name)
        labels = layout_action_labels(dataset)
        normalized_id2label = (
            {int(key): str(value) for key, value in id2label.items()}
            if id2label is not None
            else dict(enumerate(labels))
        )
        self.dataset_name = dataset
        self.precision = int(precision)
        self.max_elements = int(
            max_elements
            if max_elements is not None
            else max_elements_for_layout_action_dataset(dataset)
        )
        self.n_layer = int(n_layer)
        self.n_head = int(n_head)
        self.n_embd = int(n_embd)
        self.embd_pdrop = float(embd_pdrop)
        self.resid_pdrop = float(resid_pdrop)
        self.attn_pdrop = float(attn_pdrop)
        self.default_sampling = str(normalize_sampling_mode(default_sampling))
        self.default_top_k = int(default_top_k)
        self.default_temperature = float(default_temperature)
        self.original_dataset_name = original_dataset_name or dataset
        self.original_asset_manifest = dict(original_asset_manifest or {})
        self.id2label: dict[int, str] = normalized_id2label
        self.label2id: dict[str, int] = {
            value: key for key, value in self.id2label.items()
        }
        self.size: int = 2**self.precision
        self.element_token_width: int = ELEMENT_TOKEN_WIDTH
        self.max_token_length: int = self.max_elements * self.element_token_width + 2
        self.block_size = int(
            block_size if block_size is not None else self.max_token_length
        )
        self.no_value_token_id: int = self.size
        self.label_token_offset: int = self.size + 1
        self.copy_token_id: int = self.label_token_offset + len(self.id2label)
        self.margin_token_id: int = self.copy_token_id + 1
        self.generate_token_id: int = self.margin_token_id + 1
        self.no_obj_token_id: int = self.generate_token_id + 1
        resolved_vocab_size = (
            int(vocab_size)
            if vocab_size is not None
            else self.size + 1 + len(self.id2label) + 3 + 1 + self.max_elements + 3
        )
        self.vocab_size = resolved_vocab_size
        self.bos_token_id = self.vocab_size - 3
        self.eos_token_id = self.vocab_size - 2
        self.pad_token_id = self.vocab_size - 1

    def label_token_id(self, label_id: int) -> int:
        """Return the synthetic token id for a dataset-local label id."""
        id2label = cast(dict[int, str], self.id2label)
        if label_id not in id2label:
            raise ValueError(f"Unknown LayoutAction label id: {label_id}")

        return self.label_token_offset + int(label_id)

    def label_id_from_token(self, token_id: int) -> int | None:
        """Return a dataset-local label id for a label token id."""
        id2label = cast(dict[int, str], self.id2label)
        label_id = int(token_id) - self.label_token_offset
        if 0 <= label_id < len(id2label):
            return label_id
        return None

    def object_token_id(self, back_reference: int) -> int:
        """Return the token id for a previous-object back reference."""
        if not 1 <= back_reference <= self.max_elements:
            raise ValueError("back_reference must be in [1, max_elements]")

        return self.no_obj_token_id + back_reference

    def back_reference_from_token(self, token_id: int) -> int | None:
        """Return a previous-object back reference from a token id."""
        value = int(token_id) - self.no_obj_token_id
        if 1 <= value <= self.max_elements:
            return value
        return None

__init__

__init__(
    *,
    dataset_name: str = "rico13",
    id2label: Mapping[int, str]
    | Mapping[str, str]
    | None = None,
    precision: int = 8,
    max_elements: int | None = None,
    block_size: int | None = None,
    vocab_size: int | None = None,
    n_layer: int = 6,
    n_head: int = 8,
    n_embd: int = 512,
    embd_pdrop: float = 0.1,
    resid_pdrop: float = 0.1,
    attn_pdrop: float = 0.1,
    default_sampling: LayoutActionSamplingMode
    | str = LayoutActionSamplingMode.top_k,
    default_top_k: int = 5,
    default_temperature: float = 1.0,
    original_dataset_name: str | None = None,
    original_asset_manifest: Mapping[
        str,
        str
        | int
        | list[str]
        | dict[str, AssetManifestFile],
    ]
    | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize LayoutAction metadata and derived token ids.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
def __init__(
    self,
    *,
    dataset_name: str = "rico13",
    id2label: Mapping[int, str] | Mapping[str, str] | None = None,
    precision: int = 8,
    max_elements: int | None = None,
    block_size: int | None = None,
    vocab_size: int | None = None,
    n_layer: int = 6,
    n_head: int = 8,
    n_embd: int = 512,
    embd_pdrop: float = 0.1,
    resid_pdrop: float = 0.1,
    attn_pdrop: float = 0.1,
    default_sampling: LayoutActionSamplingMode
    | str = LayoutActionSamplingMode.top_k,
    default_top_k: int = 5,
    default_temperature: float = 1.0,
    original_dataset_name: str | None = None,
    original_asset_manifest: Mapping[
        str, str | int | list[str] | dict[str, AssetManifestFile]
    ]
    | None = None,
    model_type: str | None = None,
    transformers_version: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize LayoutAction metadata and derived token ids."""
    _ = (model_type, transformers_version)
    for derived_key in (
        "bos_token_id",
        "eos_token_id",
        "pad_token_id",
        "label2id",
        "size",
        "element_token_width",
        "max_token_length",
        "no_value_token_id",
        "label_token_offset",
        "copy_token_id",
        "margin_token_id",
        "generate_token_id",
        "no_obj_token_id",
    ):
        kwargs.pop(derived_key, None)
    _ = kwargs
    super().__init__()
    dataset = normalize_vendor_dataset_name(dataset_name)
    labels = layout_action_labels(dataset)
    normalized_id2label = (
        {int(key): str(value) for key, value in id2label.items()}
        if id2label is not None
        else dict(enumerate(labels))
    )
    self.dataset_name = dataset
    self.precision = int(precision)
    self.max_elements = int(
        max_elements
        if max_elements is not None
        else max_elements_for_layout_action_dataset(dataset)
    )
    self.n_layer = int(n_layer)
    self.n_head = int(n_head)
    self.n_embd = int(n_embd)
    self.embd_pdrop = float(embd_pdrop)
    self.resid_pdrop = float(resid_pdrop)
    self.attn_pdrop = float(attn_pdrop)
    self.default_sampling = str(normalize_sampling_mode(default_sampling))
    self.default_top_k = int(default_top_k)
    self.default_temperature = float(default_temperature)
    self.original_dataset_name = original_dataset_name or dataset
    self.original_asset_manifest = dict(original_asset_manifest or {})
    self.id2label: dict[int, str] = normalized_id2label
    self.label2id: dict[str, int] = {
        value: key for key, value in self.id2label.items()
    }
    self.size: int = 2**self.precision
    self.element_token_width: int = ELEMENT_TOKEN_WIDTH
    self.max_token_length: int = self.max_elements * self.element_token_width + 2
    self.block_size = int(
        block_size if block_size is not None else self.max_token_length
    )
    self.no_value_token_id: int = self.size
    self.label_token_offset: int = self.size + 1
    self.copy_token_id: int = self.label_token_offset + len(self.id2label)
    self.margin_token_id: int = self.copy_token_id + 1
    self.generate_token_id: int = self.margin_token_id + 1
    self.no_obj_token_id: int = self.generate_token_id + 1
    resolved_vocab_size = (
        int(vocab_size)
        if vocab_size is not None
        else self.size + 1 + len(self.id2label) + 3 + 1 + self.max_elements + 3
    )
    self.vocab_size = resolved_vocab_size
    self.bos_token_id = self.vocab_size - 3
    self.eos_token_id = self.vocab_size - 2
    self.pad_token_id = self.vocab_size - 1

label_token_id

label_token_id(label_id: int) -> int

Return the synthetic token id for a dataset-local label id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
190
191
192
193
194
195
196
def label_token_id(self, label_id: int) -> int:
    """Return the synthetic token id for a dataset-local label id."""
    id2label = cast(dict[int, str], self.id2label)
    if label_id not in id2label:
        raise ValueError(f"Unknown LayoutAction label id: {label_id}")

    return self.label_token_offset + int(label_id)

label_id_from_token

label_id_from_token(token_id: int) -> int | None

Return a dataset-local label id for a label token id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
198
199
200
201
202
203
204
def label_id_from_token(self, token_id: int) -> int | None:
    """Return a dataset-local label id for a label token id."""
    id2label = cast(dict[int, str], self.id2label)
    label_id = int(token_id) - self.label_token_offset
    if 0 <= label_id < len(id2label):
        return label_id
    return None

object_token_id

object_token_id(back_reference: int) -> int

Return the token id for a previous-object back reference.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
206
207
208
209
210
211
def object_token_id(self, back_reference: int) -> int:
    """Return the token id for a previous-object back reference."""
    if not 1 <= back_reference <= self.max_elements:
        raise ValueError("back_reference must be in [1, max_elements]")

    return self.no_obj_token_id + back_reference

back_reference_from_token

back_reference_from_token(token_id: int) -> int | None

Return a previous-object back reference from a token id.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
213
214
215
216
217
218
def back_reference_from_token(self, token_id: int) -> int | None:
    """Return a previous-object back reference from a token id."""
    value = int(token_id) - self.no_obj_token_id
    if 1 <= value <= self.max_elements:
        return value
    return None

normalize_sampling_mode

normalize_sampling_mode(
    value: LayoutActionSamplingMode | str,
) -> LayoutActionSamplingMode

Normalize a sampling-mode value.

Source code in models/layout-action/src/layout_action/configuration_layout_action.py
47
48
49
50
51
52
53
54
55
56
def normalize_sampling_mode(
    value: LayoutActionSamplingMode | str,
) -> LayoutActionSamplingMode:
    """Normalize a sampling-mode value."""
    if isinstance(value, LayoutActionSamplingMode):
        return value
    try:
        return LayoutActionSamplingMode(str(value).lower().replace("-", "_"))
    except ValueError as exc:
        raise ValueError(f"Unsupported LayoutAction sampling mode: {value}") from exc

conversion

Checkpoint conversion helpers for LayoutAction.

StateDictKeyReport dataclass

One source-to-target state-dict mapping result.

Source code in models/layout-action/src/layout_action/conversion.py
20
21
22
23
24
25
26
27
@dataclass(frozen=True)
class StateDictKeyReport:
    """One source-to-target state-dict mapping result."""

    source_key: str
    target_key: str
    source_shape: tuple[int, ...]
    loaded: bool

StateDictKeyReportDict

Bases: TypedDict

Serialized source-to-target state-dict mapping result.

Source code in models/layout-action/src/layout_action/conversion.py
30
31
32
33
34
35
36
class StateDictKeyReportDict(TypedDict):
    """Serialized source-to-target state-dict mapping result."""

    source_key: str
    target_key: str
    source_shape: tuple[int, ...]
    loaded: bool

LayoutActionConversionReport

Bases: TypedDict

Serialized LayoutAction conversion report.

Source code in models/layout-action/src/layout_action/conversion.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
class LayoutActionConversionReport(TypedDict):
    """Serialized LayoutAction conversion report."""

    checkpoint: str
    checkpoint_sha256: str
    config: dict[
        str,
        str
        | int
        | float
        | bool
        | dict[int, str]
        | dict[str, int]
        | dict[str, str | int | list[str] | dict[str, str | int]]
        | None,
    ]
    keys: list[StateDictKeyReportDict]
    missing_keys: list[str]
    unexpected_keys: list[str]

remap_layout_action_key

remap_layout_action_key(key: str) -> str

Map a vendor LayoutAction state-dict key to this package.

Source code in models/layout-action/src/layout_action/conversion.py
60
61
62
def remap_layout_action_key(key: str) -> str:
    """Map a vendor LayoutAction state-dict key to this package."""
    return key

remap_state_dict

remap_state_dict(
    state_dict: dict[str, Shaped[Tensor, "..."]],
    model: LayoutActionForCausalLM,
) -> tuple[
    dict[str, Shaped[torch.Tensor, "..."]],
    list[StateDictKeyReport],
]

Remap and report checkpoint key coverage.

Source code in models/layout-action/src/layout_action/conversion.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
def remap_state_dict(
    state_dict: dict[str, Shaped[torch.Tensor, "..."]],
    model: LayoutActionForCausalLM,
) -> tuple[dict[str, Shaped[torch.Tensor, "..."]], list[StateDictKeyReport]]:
    """Remap and report checkpoint key coverage."""
    target_keys = set(model.state_dict())
    remapped: dict[str, Shaped[torch.Tensor, "..."]] = {}
    report: list[StateDictKeyReport] = []
    for source_key, value in state_dict.items():
        target_key = remap_layout_action_key(source_key)
        loaded = target_key in target_keys
        if loaded:
            remapped[target_key] = value
        report.append(
            StateDictKeyReport(
                source_key=source_key,
                target_key=target_key,
                source_shape=tuple(value.shape),
                loaded=loaded,
            )
        )
    return remapped, report

sha256_file

sha256_file(path: str | Path) -> str

Return the SHA256 digest for a checkpoint file.

Source code in models/layout-action/src/layout_action/conversion.py
89
90
91
92
93
94
95
def sha256_file(path: str | Path) -> str:
    """Return the SHA256 digest for a checkpoint file."""
    digest = hashlib.sha256()
    with Path(path).open("rb") as f:
        for chunk in iter(lambda: f.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()

convert_layout_action_checkpoint

convert_layout_action_checkpoint(
    *,
    checkpoint: str | Path,
    output_dir: str | Path,
    config: LayoutActionConfig,
    strict: bool = True,
) -> LayoutActionConversionReport

Convert a raw vendor .pth checkpoint to HF-style files.

Parameters:

Name Type Description Default
checkpoint str | Path

Raw PyTorch state-dict path.

required
output_dir str | Path

Destination checkpoint directory.

required
config LayoutActionConfig

LayoutAction config built from dataset metadata.

required
strict bool

Whether model loading is strict.

True

Returns:

Type Description
LayoutActionConversionReport

Conversion report dictionary.

Source code in models/layout-action/src/layout_action/conversion.py
 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
def convert_layout_action_checkpoint(
    *,
    checkpoint: str | Path,
    output_dir: str | Path,
    config: LayoutActionConfig,
    strict: bool = True,
) -> LayoutActionConversionReport:
    """Convert a raw vendor ``.pth`` checkpoint to HF-style files.

    Args:
        checkpoint: Raw PyTorch state-dict path.
        output_dir: Destination checkpoint directory.
        config: LayoutAction config built from dataset metadata.
        strict: Whether model loading is strict.

    Returns:
        Conversion report dictionary.
    """
    checkpoint_path = Path(checkpoint)
    model = LayoutActionForCausalLM(config)
    raw = torch.load(checkpoint_path, map_location="cpu")
    if not isinstance(raw, dict):
        raise TypeError("LayoutAction checkpoint must be a state-dict mapping")

    remapped, report = remap_state_dict(raw, model)
    missing, unexpected = model.load_state_dict(remapped, strict=strict)
    out_dir = Path(output_dir)
    out_dir.mkdir(parents=True, exist_ok=True)
    processor = LayoutActionProcessor(LayoutActionTokenizer(config))
    model.save_pretrained(out_dir)
    processor.save_pretrained(out_dir)
    checkpoint_sha256 = sha256_file(checkpoint_path)
    config.original_asset_manifest = {
        **config.original_asset_manifest,
        "checkpoint": str(checkpoint_path),
        "checkpoint_sha256": checkpoint_sha256,
    }
    config.save_pretrained(out_dir)
    conversion_report = LayoutActionConversionReport(
        checkpoint=str(checkpoint_path),
        checkpoint_sha256=checkpoint_sha256,
        config=cast(
            dict[
                str,
                str
                | int
                | float
                | bool
                | dict[int, str]
                | dict[str, int]
                | dict[str, str | int | list[str] | dict[str, str | int]]
                | None,
            ],
            config.to_dict(),
        ),
        keys=[cast(StateDictKeyReportDict, asdict(row)) for row in report],
        missing_keys=list(missing),
        unexpected_keys=list(unexpected),
    )
    with (out_dir / "conversion_report.json").open("w", encoding="utf-8") as f:
        json.dump(conversion_report, f, indent=2, sort_keys=True)
    return conversion_report

data

Dataset metadata for LayoutAction checkpoints.

LayoutActionDatasetName

Bases: StrEnum

Dataset names supported by the LayoutAction package.

Source code in models/layout-action/src/layout_action/data.py
 9
10
11
12
13
14
15
16
class LayoutActionDatasetName(StrEnum):
    """Dataset names supported by the LayoutAction package."""

    rico13 = auto()
    layout_action_rico13 = auto()
    rico = auto()
    publaynet = auto()
    infoppt = auto()

normalize_vendor_dataset_name

normalize_vendor_dataset_name(
    dataset_name: str | LayoutActionDatasetName,
) -> str

Normalize public and release dataset aliases.

Parameters:

Name Type Description Default
dataset_name str | LayoutActionDatasetName

Dataset name or alias.

required

Returns:

Type Description
str

Canonical package dataset name.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> normalize_vendor_dataset_name("rico")
'rico13'
Source code in models/layout-action/src/layout_action/data.py
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def normalize_vendor_dataset_name(dataset_name: str | LayoutActionDatasetName) -> str:
    """Normalize public and release dataset aliases.

    Args:
        dataset_name: Dataset name or alias.

    Returns:
        Canonical package dataset name.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> normalize_vendor_dataset_name("rico")
        'rico13'
    """
    value = str(dataset_name).lower().replace("-", "_")
    if value in {"rico", "rico13", "layout_action_rico13"}:
        return "rico13"
    if value == "publaynet":
        return "publaynet"
    if value == "infoppt":
        return "infoppt"
    raise ValueError(f"Unsupported LayoutAction dataset_name: {dataset_name}")

layout_action_labels

layout_action_labels(
    dataset_name: str | LayoutActionDatasetName,
) -> tuple[str, ...]

Return the exact label order used by the LayoutAction released assets.

Source code in models/layout-action/src/layout_action/data.py
77
78
79
80
81
82
83
84
85
86
87
88
def layout_action_labels(
    dataset_name: str | LayoutActionDatasetName,
) -> tuple[str, ...]:
    """Return the exact label order used by the LayoutAction released assets."""
    dataset = normalize_vendor_dataset_name(dataset_name)
    if dataset == "rico13":
        return RICO13_LABELS
    if dataset == "publaynet":
        return PUBLAYNET_LABELS
    if dataset == "infoppt":
        return INFOPPT_LABELS
    raise ValueError(f"Unsupported LayoutAction dataset_name: {dataset_name}")

max_elements_for_layout_action_dataset

max_elements_for_layout_action_dataset(
    dataset_name: str | LayoutActionDatasetName,
) -> int

Return the released maximum element count for a LayoutAction dataset.

Source code in models/layout-action/src/layout_action/data.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
def max_elements_for_layout_action_dataset(
    dataset_name: str | LayoutActionDatasetName,
) -> int:
    """Return the released maximum element count for a LayoutAction dataset."""
    dataset = normalize_vendor_dataset_name(dataset_name)
    if dataset in {"rico13", "publaynet"}:
        return 9
    if dataset == "infoppt":
        return 20
    raise ValueError(f"Unsupported LayoutAction dataset_name: {dataset_name}")

iter_org_rico13_samples

iter_org_rico13_samples() -> None

Placeholder for the org RICO adapter.

Raises:

Type Description
NotImplementedError

Always, until the lightweight streaming adapter is wired without full dataset downloads.

Source code in models/layout-action/src/layout_action/data.py
103
104
105
106
107
108
109
110
111
112
113
114
def iter_org_rico13_samples() -> None:
    """Placeholder for the org RICO adapter.

    Raises:
        NotImplementedError: Always, until the lightweight streaming adapter is
            wired without full dataset downloads.
    """
    raise NotImplementedError(
        "RICO loading must use creative-graphic-design/Rico with the "
        "ui-screenshots-and-hierarchies-with-semantic-annotations config; "
        "the streaming adapter is not implemented in this lightweight package path."
    )

iter_org_publaynet_samples

iter_org_publaynet_samples() -> None

Placeholder for the org PubLayNet adapter.

Source code in models/layout-action/src/layout_action/data.py
117
118
119
120
121
122
def iter_org_publaynet_samples() -> None:
    """Placeholder for the org PubLayNet adapter."""
    raise NotImplementedError(
        "PubLayNet loading must avoid full downloads; use tiny builders or "
        "streaming fixtures when the adapter is implemented."
    )

iter_vendor_infoppt_samples

iter_vendor_infoppt_samples() -> None

Placeholder for original-distribution-only InfoPPT loading.

Source code in models/layout-action/src/layout_action/data.py
125
126
127
128
129
130
def iter_vendor_infoppt_samples() -> None:
    """Placeholder for original-distribution-only InfoPPT loading."""
    raise NotImplementedError(
        "InfoPPT is not available in the creative-graphic-design HF org yet; "
        "use the original distribution until it is imported."
    )

generation_layout_action

Token sampling helpers for LayoutAction.

TokenModelOutput

Bases: Protocol

Model output carrying logits.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
18
19
20
21
22
@runtime_checkable
class TokenModelOutput(Protocol):
    """Model output carrying logits."""

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

ActionTokenModel

Bases: Protocol

Minimal protocol implemented by LayoutAction token models.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
25
26
27
28
29
30
31
32
33
34
35
@runtime_checkable
class ActionTokenModel(Protocol):
    """Minimal protocol implemented by LayoutAction token models."""

    def get_block_size(self) -> int:
        """Return the maximum context length."""

    def __call__(
        self, input_ids: Int[torch.Tensor, "batch sequence"]
    ) -> TokenModelOutput:
        """Return a model output with logits."""

get_block_size

get_block_size() -> int

Return the maximum context length.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
29
30
def get_block_size(self) -> int:
    """Return the maximum context length."""

__call__

__call__(
    input_ids: Int[Tensor, "batch sequence"],
) -> TokenModelOutput

Return a model output with logits.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
32
33
34
35
def __call__(
    self, input_ids: Int[torch.Tensor, "batch sequence"]
) -> TokenModelOutput:
    """Return a model output with logits."""

LayoutActionSamplingConfig dataclass

Sampling parameters for LayoutAction token generation.

Parameters:

Name Type Description Default
mode LayoutActionSamplingMode

Greedy, multinomial, or top-k sampling.

top_k
temperature float

Positive logit temperature.

1.0
top_k int | None

Optional top-k crop size.

5

Examples:

>>> str(LayoutActionSamplingConfig().mode)
'top_k'
Source code in models/layout-action/src/layout_action/generation_layout_action.py
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@dataclass(frozen=True)
class LayoutActionSamplingConfig:
    """Sampling parameters for LayoutAction token generation.

    Args:
        mode: Greedy, multinomial, or top-k sampling.
        temperature: Positive logit temperature.
        top_k: Optional top-k crop size.

    Examples:
        >>> str(LayoutActionSamplingConfig().mode)
        'top_k'
    """

    mode: LayoutActionSamplingMode = LayoutActionSamplingMode.top_k
    temperature: float = 1.0
    top_k: int | None = 5

    @classmethod
    def from_values(
        cls,
        *,
        mode: LayoutActionSamplingMode | str,
        temperature: float,
        top_k: int | None,
    ) -> "LayoutActionSamplingConfig":
        """Build a normalized sampling config from public values."""
        return cls(
            mode=normalize_sampling_mode(mode),
            temperature=float(temperature),
            top_k=None if top_k is None else int(top_k),
        )

from_values classmethod

from_values(
    *,
    mode: LayoutActionSamplingMode | str,
    temperature: float,
    top_k: int | None,
) -> "LayoutActionSamplingConfig"

Build a normalized sampling config from public values.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@classmethod
def from_values(
    cls,
    *,
    mode: LayoutActionSamplingMode | str,
    temperature: float,
    top_k: int | None,
) -> "LayoutActionSamplingConfig":
    """Build a normalized sampling config from public values."""
    return cls(
        mode=normalize_sampling_mode(mode),
        temperature=float(temperature),
        top_k=None if top_k is None else int(top_k),
    )

top_k_logits

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

Mask logits outside the top k values exactly like the reference helper.

Parameters:

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

Logits shaped (batch, vocab).

required
k int

Number of top logits to keep.

required

Returns:

Type Description
Float[Tensor, 'batch vocab']

Logits with non-top-k entries set to negative infinity.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
def top_k_logits(
    logits: Float[torch.Tensor, "batch vocab"], k: int
) -> Float[torch.Tensor, "batch vocab"]:
    """Mask logits outside the top ``k`` values exactly like the reference helper.

    Args:
        logits: Logits shaped ``(batch, vocab)``.
        k: Number of top logits to keep.

    Returns:
        Logits with non-top-k entries set to negative infinity.
    """
    values, _ = torch.topk(logits, k)
    out = logits.clone()
    out[out < values[:, [-1]]] = -float("Inf")
    return out

sample_action_tokens

sample_action_tokens(
    model: ActionTokenModel,
    input_ids: Int[Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    sampling: LayoutActionSamplingConfig,
    forced_token_ids: Int[Tensor, "batch new_tokens"]
    | None = None,
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]

Autoregressively sample LayoutAction token ids.

Parameters:

Name Type Description Default
model ActionTokenModel

Token model returning logits.

required
input_ids Int[Tensor, 'batch sequence']

Prompt ids shaped (batch, prompt).

required
max_new_tokens int

Number of new tokens to append.

required
sampling LayoutActionSamplingConfig

Sampling parameters.

required
forced_token_ids Int[Tensor, 'batch new_tokens'] | None

Optional ids shaped (batch, max_new_tokens) with -100 for freely sampled positions.

None
generator Generator | None

Optional torch generator for multinomial sampling.

None

Returns:

Type Description
Int[Tensor, 'batch sequence']

Prompt plus sampled token ids.

Source code in models/layout-action/src/layout_action/generation_layout_action.py
 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
@torch.no_grad()
def sample_action_tokens(
    model: ActionTokenModel,
    input_ids: Int[torch.Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    sampling: LayoutActionSamplingConfig,
    forced_token_ids: Int[torch.Tensor, "batch new_tokens"] | None = None,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]:
    """Autoregressively sample LayoutAction token ids.

    Args:
        model: Token model returning logits.
        input_ids: Prompt ids shaped ``(batch, prompt)``.
        max_new_tokens: Number of new tokens to append.
        sampling: Sampling parameters.
        forced_token_ids: Optional ids shaped ``(batch, max_new_tokens)`` with
            ``-100`` for freely sampled positions.
        generator: Optional torch generator for multinomial sampling.

    Returns:
        Prompt plus sampled token ids.
    """
    block_size = model.get_block_size()
    sequence = input_ids.long()
    for step in range(max_new_tokens):
        if forced_token_ids is not None:
            forced = forced_token_ids[:, step]
            if bool(torch.all(forced.ge(0))):
                sequence = torch.cat((sequence, forced.unsqueeze(1)), dim=1)
                continue
        context = (
            sequence if sequence.size(1) <= block_size else sequence[:, -block_size:]
        )
        raw_output = model(context)
        logits = raw_output.logits
        next_logits = logits[:, -1, :] / sampling.temperature
        if (
            sampling.mode is LayoutActionSamplingMode.top_k
            and sampling.top_k is not None
        ):
            next_logits = top_k_logits(next_logits, sampling.top_k)
        probs = F.softmax(next_logits, dim=-1)
        if sampling.mode is LayoutActionSamplingMode.greedy:
            _, next_id = torch.topk(probs, k=1, dim=-1)
        else:
            next_id = torch.multinomial(probs, num_samples=1, generator=generator)
        if forced_token_ids is not None:
            forced = forced_token_ids[:, step].unsqueeze(1)
            next_id = torch.where(forced.ge(0), forced, next_id)
        sequence = torch.cat((sequence, next_id), dim=1)
    return sequence

modeling_layout_action

PyTorch model wrapper for LayoutAction.

LayoutActionCausalSelfAttention

Bases: Module

Checkpoint-compatible masked multi-head self-attention.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class LayoutActionCausalSelfAttention(nn.Module):
    """Checkpoint-compatible masked multi-head self-attention."""

    mask: Float[torch.Tensor, "1 1 block block"]

    def __init__(
        self, config: LayoutActionConfig, mask: Float[torch.Tensor, "1 1 block block"]
    ) -> None:
        """Initialize key, query, value, and output projections."""
        super().__init__()
        if config.n_embd % config.n_head != 0:
            raise ValueError("n_embd must be divisible by n_head")

        self.key = nn.Linear(config.n_embd, config.n_embd)
        self.query = nn.Linear(config.n_embd, config.n_embd)
        self.value = nn.Linear(config.n_embd, config.n_embd)
        self.attn_drop = nn.Dropout(config.attn_pdrop)
        self.resid_drop = nn.Dropout(config.resid_pdrop)
        self.proj = nn.Linear(config.n_embd, config.n_embd)
        self.register_buffer("mask", mask.clone())
        self.n_head = config.n_head

    def forward(
        self, x: Float[torch.Tensor, "batch sequence channels"]
    ) -> Float[torch.Tensor, "batch sequence channels"]:
        """Apply causal self-attention."""
        batch, steps, channels = x.size()
        key = self.key(x).view(batch, steps, self.n_head, channels // self.n_head)
        query = self.query(x).view(batch, steps, self.n_head, channels // self.n_head)
        value = self.value(x).view(batch, steps, self.n_head, channels // self.n_head)
        key = key.transpose(1, 2)
        query = query.transpose(1, 2)
        value = value.transpose(1, 2)
        att = (query @ key.transpose(-2, -1)) * (1.0 / math.sqrt(key.size(-1)))
        att = att.masked_fill(self.mask[:, :, :steps, :steps] == 0, float("-inf"))
        att = F.softmax(att, dim=-1)
        att = self.attn_drop(att)
        y = att @ value
        y = y.transpose(1, 2).contiguous().view(batch, steps, channels)
        return self.resid_drop(self.proj(y))

__init__

__init__(
    config: LayoutActionConfig,
    mask: Float[Tensor, "1 1 block block"],
) -> None

Initialize key, query, value, and output projections.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
def __init__(
    self, config: LayoutActionConfig, mask: Float[torch.Tensor, "1 1 block block"]
) -> None:
    """Initialize key, query, value, and output projections."""
    super().__init__()
    if config.n_embd % config.n_head != 0:
        raise ValueError("n_embd must be divisible by n_head")

    self.key = nn.Linear(config.n_embd, config.n_embd)
    self.query = nn.Linear(config.n_embd, config.n_embd)
    self.value = nn.Linear(config.n_embd, config.n_embd)
    self.attn_drop = nn.Dropout(config.attn_pdrop)
    self.resid_drop = nn.Dropout(config.resid_pdrop)
    self.proj = nn.Linear(config.n_embd, config.n_embd)
    self.register_buffer("mask", mask.clone())
    self.n_head = config.n_head

forward

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

Apply causal self-attention.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
def forward(
    self, x: Float[torch.Tensor, "batch sequence channels"]
) -> Float[torch.Tensor, "batch sequence channels"]:
    """Apply causal self-attention."""
    batch, steps, channels = x.size()
    key = self.key(x).view(batch, steps, self.n_head, channels // self.n_head)
    query = self.query(x).view(batch, steps, self.n_head, channels // self.n_head)
    value = self.value(x).view(batch, steps, self.n_head, channels // self.n_head)
    key = key.transpose(1, 2)
    query = query.transpose(1, 2)
    value = value.transpose(1, 2)
    att = (query @ key.transpose(-2, -1)) * (1.0 / math.sqrt(key.size(-1)))
    att = att.masked_fill(self.mask[:, :, :steps, :steps] == 0, float("-inf"))
    att = F.softmax(att, dim=-1)
    att = self.attn_drop(att)
    y = att @ value
    y = y.transpose(1, 2).contiguous().view(batch, steps, channels)
    return self.resid_drop(self.proj(y))

LayoutActionBlock

Bases: Module

Checkpoint-compatible GPT block.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class LayoutActionBlock(nn.Module):
    """Checkpoint-compatible GPT block."""

    def __init__(
        self, config: LayoutActionConfig, mask: Float[torch.Tensor, "1 1 block block"]
    ) -> None:
        """Initialize layer norms, self-attention, and MLP."""
        super().__init__()
        self.ln1 = nn.LayerNorm(config.n_embd)
        self.ln2 = nn.LayerNorm(config.n_embd)
        self.attn = LayoutActionCausalSelfAttention(config, mask)
        self.mlp = nn.Sequential(
            nn.Linear(config.n_embd, 4 * config.n_embd),
            nn.GELU(),
            nn.Linear(4 * config.n_embd, config.n_embd),
            nn.Dropout(config.resid_pdrop),
        )

    def forward(
        self, x: Float[torch.Tensor, "batch sequence channels"]
    ) -> Float[torch.Tensor, "batch sequence channels"]:
        """Run one transformer block."""
        x = x + self.attn(self.ln1(x))
        return x + self.mlp(self.ln2(x))

__init__

__init__(
    config: LayoutActionConfig,
    mask: Float[Tensor, "1 1 block block"],
) -> None

Initialize layer norms, self-attention, and MLP.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def __init__(
    self, config: LayoutActionConfig, mask: Float[torch.Tensor, "1 1 block block"]
) -> None:
    """Initialize layer norms, self-attention, and MLP."""
    super().__init__()
    self.ln1 = nn.LayerNorm(config.n_embd)
    self.ln2 = nn.LayerNorm(config.n_embd)
    self.attn = LayoutActionCausalSelfAttention(config, mask)
    self.mlp = nn.Sequential(
        nn.Linear(config.n_embd, 4 * config.n_embd),
        nn.GELU(),
        nn.Linear(4 * config.n_embd, config.n_embd),
        nn.Dropout(config.resid_pdrop),
    )

forward

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

Run one transformer block.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
82
83
84
85
86
87
def forward(
    self, x: Float[torch.Tensor, "batch sequence channels"]
) -> Float[torch.Tensor, "batch sequence channels"]:
    """Run one transformer block."""
    x = x + self.attn(self.ln1(x))
    return x + self.mlp(self.ln2(x))

LayoutActionForCausalLM

Bases: PreTrainedModel

Transformers PreTrainedModel for LayoutAction token prediction.

Parameters:

Name Type Description Default
config LayoutActionConfig

LayoutAction architecture and vocabulary metadata.

required

Examples:

>>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
>>> model = LayoutActionForCausalLM(config)
>>> out = model(torch.tensor([[config.bos_token_id]]))
>>> out.logits.shape[-1] == config.vocab_size
True
Source code in models/layout-action/src/layout_action/modeling_layout_action.py
 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
class LayoutActionForCausalLM(PreTrainedModel):
    """Transformers ``PreTrainedModel`` for LayoutAction token prediction.

    Args:
        config: LayoutAction architecture and vocabulary metadata.

    Examples:
        >>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
        >>> model = LayoutActionForCausalLM(config)
        >>> out = model(torch.tensor([[config.bos_token_id]]))
        >>> out.logits.shape[-1] == config.vocab_size
        True
    """

    config_class = LayoutActionConfig
    base_model_prefix = "layout_action"
    main_input_name = "input_ids"
    _tied_weights_keys: dict[str, str] = {}

    def __init__(self, config: LayoutActionConfig) -> None:
        """Initialize checkpoint-compatible GPT modules."""
        super().__init__(config)
        self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd)
        self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd))
        self.drop = nn.Dropout(config.embd_pdrop)
        mask = torch.tril(torch.ones(config.block_size, config.block_size)).view(
            1, 1, config.block_size, config.block_size
        )
        self.blocks = nn.ModuleList(
            [LayoutActionBlock(config, mask) for _ in range(config.n_layer)]
        )
        self.ln_f = nn.LayerNorm(config.n_embd)
        self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
        self.block_size = config.block_size
        self.all_tied_weights_keys = dict(self._tied_weights_keys)
        self.post_init()

    def get_block_size(self) -> int:
        """Return the maximum context length."""
        return self.block_size

    def get_input_embeddings(self) -> nn.Embedding:
        """Return token embeddings."""
        return self.tok_emb

    def set_input_embeddings(self, value: nn.Embedding) -> None:
        """Replace token embeddings."""
        self.tok_emb = value

    def forward(
        self,
        input_ids: Int[torch.Tensor, "batch sequence"],
        attention_mask: Bool[torch.Tensor, "batch sequence"] | None = None,
        labels: Int[torch.Tensor, "batch sequence"] | None = None,
        return_dict: bool | None = None,
        output_hidden_states: bool | None = None,
        output_attentions: bool | None = None,
    ) -> CausalLMOutputWithCrossAttentions | tuple[Shaped[torch.Tensor, "..."], ...]:
        """Run a standard causal language-model forward pass.

        Args:
            input_ids: Token ids shaped ``(batch, sequence)``.
            attention_mask: Accepted for Transformers compatibility; causal
                masking follows the checkpoint implementation.
            labels: Optional next-token labels.
            return_dict: Whether to return a dataclass output.
            output_hidden_states: Include final hidden states.
            output_attentions: Accepted for API compatibility; attentions are
                not materialized by the checkpoint-compatible blocks.

        Returns:
            Causal LM output or tuple.

        Raises:
            ValueError: If sequence length exceeds ``block_size``.
        """
        _ = (attention_mask, output_attentions)
        use_return_dict = (
            self.config.use_return_dict if return_dict is None else return_dict
        )
        batch, steps = input_ids.size()
        if steps > self.block_size:
            raise ValueError("Cannot forward; model block size is exhausted.")

        token_embeddings = self.tok_emb(input_ids)
        position_embeddings = self.pos_emb[:, :steps, :]
        hidden_states = self.drop(token_embeddings + position_embeddings)
        for block in self.blocks:
            hidden_states = block(hidden_states)
        hidden_states = self.ln_f(hidden_states)
        logits = self.head(hidden_states)
        loss = None
        if labels is not None:
            target = labels.masked_fill(labels == self.config.pad_token_id, -100)
            loss = F.cross_entropy(
                logits.view(-1, logits.size(-1)),
                target.view(-1),
                ignore_index=-100,
            )
        if not use_return_dict:
            values: tuple[Shaped[torch.Tensor, "..."], ...]
            values = (logits,) if loss is None else (loss, logits)
            if output_hidden_states:
                values = (*values, hidden_states)
            return values
        return CausalLMOutputWithCrossAttentions(
            loss=cast(torch.FloatTensor | None, loss),
            logits=logits,
            hidden_states=(hidden_states,) if output_hidden_states else None,
        )

    @torch.no_grad()
    def generate(
        self,
        input_ids: Int[torch.Tensor, "batch sequence"],
        *,
        max_new_tokens: int,
        temperature: float = 1.0,
        top_k: int | None = None,
        do_sample: bool = False,
        forced_token_ids: Int[torch.Tensor, "batch sequence"] | None = None,
        generator: torch.Generator | None = None,
    ) -> Int[torch.Tensor, "batch sequence"]:
        """Generate token ids with the reference sampling loop.

        Args:
            input_ids: Prompt token ids.
            max_new_tokens: Number of new tokens.
            temperature: Sampling temperature.
            top_k: Optional top-k crop size.
            do_sample: Whether to use multinomial sampling. If ``False``, greedy
                decoding is used.
            forced_token_ids: Optional ids to force at each generation step.
            generator: Optional torch generator for multinomial sampling.

        Returns:
            Prompt plus generated token ids.
        """
        mode = (
            LayoutActionSamplingMode.top_k
            if do_sample and top_k is not None
            else LayoutActionSamplingMode.multinomial
            if do_sample
            else LayoutActionSamplingMode.greedy
        )
        return sample_action_tokens(
            self,
            input_ids,
            max_new_tokens=max_new_tokens,
            sampling=LayoutActionSamplingConfig(
                mode=mode,
                temperature=temperature,
                top_k=top_k,
            ),
            forced_token_ids=forced_token_ids,
            generator=generator,
        )

__init__

__init__(config: LayoutActionConfig) -> None

Initialize checkpoint-compatible GPT modules.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
def __init__(self, config: LayoutActionConfig) -> None:
    """Initialize checkpoint-compatible GPT modules."""
    super().__init__(config)
    self.tok_emb = nn.Embedding(config.vocab_size, config.n_embd)
    self.pos_emb = nn.Parameter(torch.zeros(1, config.block_size, config.n_embd))
    self.drop = nn.Dropout(config.embd_pdrop)
    mask = torch.tril(torch.ones(config.block_size, config.block_size)).view(
        1, 1, config.block_size, config.block_size
    )
    self.blocks = nn.ModuleList(
        [LayoutActionBlock(config, mask) for _ in range(config.n_layer)]
    )
    self.ln_f = nn.LayerNorm(config.n_embd)
    self.head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
    self.block_size = config.block_size
    self.all_tied_weights_keys = dict(self._tied_weights_keys)
    self.post_init()

get_block_size

get_block_size() -> int

Return the maximum context length.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
127
128
129
def get_block_size(self) -> int:
    """Return the maximum context length."""
    return self.block_size

get_input_embeddings

get_input_embeddings() -> nn.Embedding

Return token embeddings.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
131
132
133
def get_input_embeddings(self) -> nn.Embedding:
    """Return token embeddings."""
    return self.tok_emb

set_input_embeddings

set_input_embeddings(value: Embedding) -> None

Replace token embeddings.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
135
136
137
def set_input_embeddings(self, value: nn.Embedding) -> None:
    """Replace token embeddings."""
    self.tok_emb = value

forward

forward(
    input_ids: Int[Tensor, "batch sequence"],
    attention_mask: Bool[Tensor, "batch sequence"]
    | None = None,
    labels: Int[Tensor, "batch sequence"] | None = None,
    return_dict: bool | None = None,
    output_hidden_states: bool | None = None,
    output_attentions: bool | None = None,
) -> (
    CausalLMOutputWithCrossAttentions
    | tuple[Shaped[torch.Tensor, "..."], ...]
)

Run a standard causal language-model forward pass.

Parameters:

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

Token ids shaped (batch, sequence).

required
attention_mask Bool[Tensor, 'batch sequence'] | None

Accepted for Transformers compatibility; causal masking follows the checkpoint implementation.

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

Optional next-token labels.

None
return_dict bool | None

Whether to return a dataclass output.

None
output_hidden_states bool | None

Include final hidden states.

None
output_attentions bool | None

Accepted for API compatibility; attentions are not materialized by the checkpoint-compatible blocks.

None

Returns:

Type Description
CausalLMOutputWithCrossAttentions | tuple[Shaped[Tensor, '...'], ...]

Causal LM output or tuple.

Raises:

Type Description
ValueError

If sequence length exceeds block_size.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
def forward(
    self,
    input_ids: Int[torch.Tensor, "batch sequence"],
    attention_mask: Bool[torch.Tensor, "batch sequence"] | None = None,
    labels: Int[torch.Tensor, "batch sequence"] | None = None,
    return_dict: bool | None = None,
    output_hidden_states: bool | None = None,
    output_attentions: bool | None = None,
) -> CausalLMOutputWithCrossAttentions | tuple[Shaped[torch.Tensor, "..."], ...]:
    """Run a standard causal language-model forward pass.

    Args:
        input_ids: Token ids shaped ``(batch, sequence)``.
        attention_mask: Accepted for Transformers compatibility; causal
            masking follows the checkpoint implementation.
        labels: Optional next-token labels.
        return_dict: Whether to return a dataclass output.
        output_hidden_states: Include final hidden states.
        output_attentions: Accepted for API compatibility; attentions are
            not materialized by the checkpoint-compatible blocks.

    Returns:
        Causal LM output or tuple.

    Raises:
        ValueError: If sequence length exceeds ``block_size``.
    """
    _ = (attention_mask, output_attentions)
    use_return_dict = (
        self.config.use_return_dict if return_dict is None else return_dict
    )
    batch, steps = input_ids.size()
    if steps > self.block_size:
        raise ValueError("Cannot forward; model block size is exhausted.")

    token_embeddings = self.tok_emb(input_ids)
    position_embeddings = self.pos_emb[:, :steps, :]
    hidden_states = self.drop(token_embeddings + position_embeddings)
    for block in self.blocks:
        hidden_states = block(hidden_states)
    hidden_states = self.ln_f(hidden_states)
    logits = self.head(hidden_states)
    loss = None
    if labels is not None:
        target = labels.masked_fill(labels == self.config.pad_token_id, -100)
        loss = F.cross_entropy(
            logits.view(-1, logits.size(-1)),
            target.view(-1),
            ignore_index=-100,
        )
    if not use_return_dict:
        values: tuple[Shaped[torch.Tensor, "..."], ...]
        values = (logits,) if loss is None else (loss, logits)
        if output_hidden_states:
            values = (*values, hidden_states)
        return values
    return CausalLMOutputWithCrossAttentions(
        loss=cast(torch.FloatTensor | None, loss),
        logits=logits,
        hidden_states=(hidden_states,) if output_hidden_states else None,
    )

generate

generate(
    input_ids: Int[Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    temperature: float = 1.0,
    top_k: int | None = None,
    do_sample: bool = False,
    forced_token_ids: Int[Tensor, "batch sequence"]
    | None = None,
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]

Generate token ids with the reference sampling loop.

Parameters:

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

Prompt token ids.

required
max_new_tokens int

Number of new tokens.

required
temperature float

Sampling temperature.

1.0
top_k int | None

Optional top-k crop size.

None
do_sample bool

Whether to use multinomial sampling. If False, greedy decoding is used.

False
forced_token_ids Int[Tensor, 'batch sequence'] | None

Optional ids to force at each generation step.

None
generator Generator | None

Optional torch generator for multinomial sampling.

None

Returns:

Type Description
Int[Tensor, 'batch sequence']

Prompt plus generated token ids.

Source code in models/layout-action/src/layout_action/modeling_layout_action.py
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
@torch.no_grad()
def generate(
    self,
    input_ids: Int[torch.Tensor, "batch sequence"],
    *,
    max_new_tokens: int,
    temperature: float = 1.0,
    top_k: int | None = None,
    do_sample: bool = False,
    forced_token_ids: Int[torch.Tensor, "batch sequence"] | None = None,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch sequence"]:
    """Generate token ids with the reference sampling loop.

    Args:
        input_ids: Prompt token ids.
        max_new_tokens: Number of new tokens.
        temperature: Sampling temperature.
        top_k: Optional top-k crop size.
        do_sample: Whether to use multinomial sampling. If ``False``, greedy
            decoding is used.
        forced_token_ids: Optional ids to force at each generation step.
        generator: Optional torch generator for multinomial sampling.

    Returns:
        Prompt plus generated token ids.
    """
    mode = (
        LayoutActionSamplingMode.top_k
        if do_sample and top_k is not None
        else LayoutActionSamplingMode.multinomial
        if do_sample
        else LayoutActionSamplingMode.greedy
    )
    return sample_action_tokens(
        self,
        input_ids,
        max_new_tokens=max_new_tokens,
        sampling=LayoutActionSamplingConfig(
            mode=mode,
            temperature=temperature,
            top_k=top_k,
        ),
        forced_token_ids=forced_token_ids,
        generator=generator,
    )

pipeline_layout_action

Pipeline wrapper for LayoutAction generation.

LayoutActionPipeline

Bases: LayoutGenerationPipeline

Compose a LayoutAction model and processor for layout generation.

Parameters:

Name Type Description Default
model LayoutActionForCausalLM

Converted LayoutAction causal LM.

required
processor LayoutActionProcessor

Matching processor/tokenizer.

required
config LayoutActionConfig | None

Optional root pipeline config. Defaults to model.config.

None

Examples:

>>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
>>> pipe = LayoutActionPipeline(
...     model=LayoutActionForCausalLM(config),
...     processor=LayoutActionProcessor(LayoutActionTokenizer(config)),
...     config=config,
... )
>>> pipe.config.model_type
'layout-action'
Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
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
class LayoutActionPipeline(LayoutGenerationPipeline):
    """Compose a LayoutAction model and processor for layout generation.

    Args:
        model: Converted LayoutAction causal LM.
        processor: Matching processor/tokenizer.
        config: Optional root pipeline config. Defaults to ``model.config``.

    Examples:
        >>> config = LayoutActionConfig(n_layer=1, n_head=2, n_embd=16, max_elements=1)
        >>> pipe = LayoutActionPipeline(
        ...     model=LayoutActionForCausalLM(config),
        ...     processor=LayoutActionProcessor(LayoutActionTokenizer(config)),
        ...     config=config,
        ... )
        >>> pipe.config.model_type
        'layout-action'
    """

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

    config: LayoutActionConfig
    model: LayoutActionForCausalLM
    processor: LayoutActionProcessor

    def __init__(
        self,
        model: LayoutActionForCausalLM,
        processor: LayoutActionProcessor,
        config: LayoutActionConfig | None = None,
    ) -> None:
        """Initialize the pipeline."""
        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],
    ) -> LayoutActionPipeline:
        """Build a pipeline from loaded components."""
        return cls(
            config=cast(LayoutActionConfig, config),
            model=cast(LayoutActionForCausalLM, components["model"]),
            processor=cast(LayoutActionProcessor, 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: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        num_inference_steps: int | None = None,
        output_type: OutputType = "dataclass",
        return_intermediates: bool = False,
        sampling: Literal["greedy", "multinomial", "top_k"] = "top_k",
        temperature: float = 1.0,
        top_k: int | None = 5,
    ) -> LayoutGenerationOutput | LayoutActionOutputDict:  # ty: ignore[invalid-method-override]
        """Generate a layout through the public LayoutAction interface."""
        encoded = self.processor(
            condition_type=condition_type,
            bbox=bbox,
            labels=labels,
            mask=mask,
            num_elements=num_elements,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            batch_size=batch_size,
            return_tensors="pt",
        )
        model_device = next(self.model.parameters()).device
        prepared_generator = self.prepare_generator(
            generator=generator,
            seed=seed,
            device=model_device,
        )
        input_ids = encoded["input_ids"].to(model_device)
        forced_token_ids = encoded.get("forced_token_ids")
        if isinstance(forced_token_ids, torch.Tensor):
            forced_token_ids = forced_token_ids.to(model_device)
        max_new_tokens = (
            int(num_inference_steps)
            if num_inference_steps is not None
            else int(encoded["max_new_tokens"])
        )
        was_training = self.model.training
        self.model.eval()
        try:
            sequences = self.model.generate(
                input_ids,
                max_new_tokens=max_new_tokens,
                temperature=temperature,
                top_k=top_k,
                do_sample=sampling != "greedy",
                forced_token_ids=forced_token_ids,
                generator=prepared_generator,
            )
        finally:
            self.model.train(was_training)
        return self.processor.post_process_layouts(
            sequences,
            output_type=output_type,
            return_intermediates=return_intermediates,
        )

__init__

__init__(
    model: LayoutActionForCausalLM,
    processor: LayoutActionProcessor,
    config: LayoutActionConfig | None = None,
) -> None

Initialize the pipeline.

Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
 99
100
101
102
103
104
105
106
107
108
109
def __init__(
    self,
    model: LayoutActionForCausalLM,
    processor: LayoutActionProcessor,
    config: LayoutActionConfig | None = None,
) -> None:
    """Initialize the pipeline."""
    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: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
    sampling: Literal[
        "greedy", "multinomial", "top_k"
    ] = "top_k",
    temperature: float = 1.0,
    top_k: int | None = 5,
) -> LayoutGenerationOutput | LayoutActionOutputDict

Generate a layout through the public LayoutAction interface.

Source code in models/layout-action/src/layout_action/pipeline_layout_action.py
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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    seed: int | None = None,
    generator: torch.Generator | None = None,
    condition_type: ConditionType | str = ConditionType.unconditional,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    num_inference_steps: int | None = None,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
    sampling: Literal["greedy", "multinomial", "top_k"] = "top_k",
    temperature: float = 1.0,
    top_k: int | None = 5,
) -> LayoutGenerationOutput | LayoutActionOutputDict:  # ty: ignore[invalid-method-override]
    """Generate a layout through the public LayoutAction interface."""
    encoded = self.processor(
        condition_type=condition_type,
        bbox=bbox,
        labels=labels,
        mask=mask,
        num_elements=num_elements,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
        batch_size=batch_size,
        return_tensors="pt",
    )
    model_device = next(self.model.parameters()).device
    prepared_generator = self.prepare_generator(
        generator=generator,
        seed=seed,
        device=model_device,
    )
    input_ids = encoded["input_ids"].to(model_device)
    forced_token_ids = encoded.get("forced_token_ids")
    if isinstance(forced_token_ids, torch.Tensor):
        forced_token_ids = forced_token_ids.to(model_device)
    max_new_tokens = (
        int(num_inference_steps)
        if num_inference_steps is not None
        else int(encoded["max_new_tokens"])
    )
    was_training = self.model.training
    self.model.eval()
    try:
        sequences = self.model.generate(
            input_ids,
            max_new_tokens=max_new_tokens,
            temperature=temperature,
            top_k=top_k,
            do_sample=sampling != "greedy",
            forced_token_ids=forced_token_ids,
            generator=prepared_generator,
        )
    finally:
        self.model.train(was_training)
    return self.processor.post_process_layouts(
        sequences,
        output_type=output_type,
        return_intermediates=return_intermediates,
    )

processing_layout_action

Processor for LayoutAction conditions and output decoding.

LayoutActionOutputDict

Bases: TypedDict

Dictionary form of the LayoutAction public output.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
27
28
29
30
31
32
33
34
35
class LayoutActionOutputDict(TypedDict, total=False):
    """Dictionary form of the LayoutAction public output."""

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

LayoutActionProcessor

Bases: ProcessorMixin

Prepare LayoutAction prompts and decode generated action sequences.

Parameters:

Name Type Description Default
tokenizer LayoutActionTokenizer

LayoutAction tokenizer.

required

Examples:

>>> processor = LayoutActionProcessor(LayoutActionTokenizer(LayoutActionConfig(max_elements=1)))
>>> encoded = processor(condition_type="unconditional")
>>> encoded["input_ids"].shape
torch.Size([1, 1])
Source code in models/layout-action/src/layout_action/processing_layout_action.py
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
class LayoutActionProcessor(ProcessorMixin):
    """Prepare LayoutAction prompts and decode generated action sequences.

    Args:
        tokenizer: LayoutAction tokenizer.

    Examples:
        >>> processor = LayoutActionProcessor(LayoutActionTokenizer(LayoutActionConfig(max_elements=1)))
        >>> encoded = processor(condition_type="unconditional")
        >>> encoded["input_ids"].shape
        torch.Size([1, 1])
    """

    attributes = ["tokenizer"]
    tokenizer_class = "LayoutActionTokenizer"

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

    @property
    def config(self) -> LayoutActionConfig:
        """Return the paired LayoutAction config."""
        return self.tokenizer.config

    def __call__(
        self,
        *,
        condition_type: ConditionType | str = ConditionType.unconditional,
        bbox: Float[torch.Tensor, "batch elements 4"]
        | Float[np.ndarray, "batch elements 4"]
        | Sequence[ArrayLikeInput]
        | None = None,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        mask: Bool[torch.Tensor, "batch elements"]
        | Bool[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput]
        | None = None,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        batch_size: int = 1,
        return_tensors: Literal["pt"] = "pt",
    ) -> BatchEncoding:
        """Encode a public generation condition.

        Args:
            condition_type: Canonical condition or supported release alias.
            bbox: Optional public boxes for completion prompts.
            labels: Optional labels for label/completion prompts.
            mask: Optional valid-element mask.
            num_elements: Optional element count or completion prefix length.
            box_format: Input box format.
            normalized: Whether boxes are normalized.
            canvas_size: Canvas size for pixel boxes.
            batch_size: Batch size for unconditional generation.
            return_tensors: Tensor framework. Only ``pt`` is supported.

        Returns:
            Batch encoding with prompt ids and optional forced token ids.

        Raises:
            NotImplementedError: If the condition is unsupported by LayoutAction.
            ValueError: If required condition payloads are missing.
        """
        if return_tensors != "pt":
            raise ValueError("LayoutActionProcessor only supports return_tensors='pt'")

        condition = normalize_condition_type(condition_type)
        if condition not in SUPPORTED_CONDITIONS:
            raise NotImplementedError(f"LayoutAction does not support {condition}.")

        if condition is ConditionType.unconditional:
            input_ids = torch.full(
                (int(batch_size), 1), self.config.bos_token_id, dtype=torch.long
            )
            return BatchEncoding(
                {
                    "input_ids": input_ids,
                    "attention_mask": torch.ones_like(input_ids),
                    "max_new_tokens": self.config.max_token_length,
                }
            )
        if labels is None:
            raise ValueError(f"{condition} generation requires labels")

        if bbox is None:
            label_tensor = torch.as_tensor(labels)
            if label_tensor.ndim == 1:
                label_tensor = label_tensor.unsqueeze(0)
            mask_tensor = (
                torch.ones_like(label_tensor, dtype=torch.bool)
                if mask is None
                else torch.as_tensor(mask, dtype=torch.bool)
            )
            if mask_tensor.ndim == 1:
                mask_tensor = mask_tensor.unsqueeze(0)
            bbox_tensor = torch.zeros(
                (*label_tensor.shape, 4),
                dtype=torch.float32,
                device=label_tensor.device,
            )
        else:
            bbox_tensor, label_tensor, mask_tensor = prepare_layout_tensors(
                bbox=bbox,
                labels=self._labels_to_ids(labels),
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
                clamp_converted_normalized=True,
            )
        full = self.tokenizer.encode_layout(
            bbox=bbox_tensor,
            labels=label_tensor.long(),
            mask=mask_tensor.bool(),
        )
        if condition is ConditionType.label:
            input_ids = full[:, :1]
            forced = torch.full(
                (full.size(0), self.config.max_token_length),
                -100,
                dtype=torch.long,
                device=full.device,
            )
            for step in range(
                0,
                self.config.max_elements * self.config.element_token_width,
                self.config.element_token_width,
            ):
                source_index = step + 1
                if source_index < full.size(1):
                    forced[:, step] = full[:, source_index]
            return BatchEncoding(
                {
                    "input_ids": input_ids,
                    "attention_mask": torch.ones_like(input_ids),
                    "forced_token_ids": forced,
                    "max_new_tokens": self.config.max_token_length,
                }
            )
        prefix_elements = self._prefix_elements(num_elements, mask_tensor)
        prefix_length = 1 + prefix_elements * self.config.element_token_width
        input_ids = full[:, :prefix_length]
        remaining = max(0, self.config.max_token_length + 1 - input_ids.size(1))
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "max_new_tokens": remaining,
            }
        )

    def post_process_layouts(
        self,
        sequences: Int[torch.Tensor, "batch tokens"],
        *,
        output_type: OutputType = "dataclass",
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | LayoutActionOutputDict:
        """Decode generated sequences to the common output schema."""
        decoded = self.tokenizer.decode_action_tokens(
            sequences.detach().cpu(),
            return_actions=return_intermediates,
        )
        intermediates = None
        if return_intermediates:
            intermediates = {"actions": decoded.get("actions")}
        output = LayoutGenerationOutput(
            bbox=cast(Float[torch.Tensor, "batch elements 4"], decoded["bbox"]),
            labels=cast(Int[torch.Tensor, "batch elements"], decoded["labels"]),
            mask=cast(Bool[torch.Tensor, "batch elements"], decoded["mask"]),
            id2label=dict(cast(dict[int, str], self.config.id2label)),
            sequences=sequences.detach().cpu(),
            intermediates=intermediates,
        )
        if output_type == "dict":
            return cast(LayoutActionOutputDict, dict(output))
        if output_type == "dataclass":
            return output
        raise ValueError(f"Unsupported output_type: {output_type}")

    def save_pretrained(
        self,
        save_directory: str | PathLike[str],
        push_to_hub: bool = False,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Save processor and tokenizer metadata."""
        _ = (push_to_hub, kwargs)
        out_dir = Path(save_directory)
        out_dir.mkdir(parents=True, exist_ok=True)
        self.tokenizer.save_pretrained(out_dir)
        with (out_dir / PROCESSOR_CONFIG_FILE).open("w", encoding="utf-8") as f:
            json.dump({"processor_class": self.__class__.__name__}, f, indent=2)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutActionProcessor":
        """Load processor metadata from a checkpoint directory or Hub repo id."""
        tokenizer = LayoutActionTokenizer.from_pretrained(
            pretrained_model_name_or_path,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            **kwargs,
        )
        return cls(tokenizer=tokenizer)

    def _labels_to_ids(
        self,
        labels: Int[torch.Tensor, "batch elements"]
        | Int[np.ndarray, "batch elements"]
        | Sequence[ArrayLikeInput],
    ) -> Int[torch.Tensor, "batch elements"]:
        if isinstance(labels, torch.Tensor):
            return labels.long()
        label_array = np.asarray(labels, dtype=object)
        if all(not isinstance(label, str) for label in label_array.flatten()):
            return torch.as_tensor(labels, dtype=torch.long)
        label2id = cast(dict[str, int], self.config.label2id)
        vectorized = np.vectorize(lambda label: label2id[str(label)])
        return torch.as_tensor(vectorized(label_array), dtype=torch.long)

    def _prefix_elements(
        self,
        num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None,
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> int:
        if num_elements is None:
            valid_counts = mask.sum(dim=1)
            return int(valid_counts.min().item())
        if isinstance(num_elements, int):
            return int(num_elements)
        tensor = torch.as_tensor(num_elements)
        return int(tensor.min().item())

config property

config: LayoutActionConfig

Return the paired LayoutAction config.

__init__

__init__(tokenizer: LayoutActionTokenizer) -> None

Initialize the processor.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
62
63
64
65
def __init__(self, tokenizer: LayoutActionTokenizer) -> None:
    """Initialize the processor."""
    self.tokenizer = tokenizer
    self.chat_template = None

__call__

__call__(
    *,
    condition_type: ConditionType
    | str = ConditionType.unconditional,
    bbox: Float[Tensor, "batch elements 4"]
    | Float[ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[Tensor, "batch elements"]
    | Int[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[Tensor, "batch elements"]
    | Bool[ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int
    | list[int]
    | Int[Tensor, "batch"]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding

Encode a public generation condition.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition or supported release alias.

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

Optional public boxes for completion prompts.

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

Optional labels for label/completion prompts.

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

Optional valid-element mask.

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

Optional element count or completion prefix length.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether boxes are normalized.

True
canvas_size tuple[int, int] | None

Canvas size for pixel boxes.

None
batch_size int

Batch size for unconditional generation.

1
return_tensors Literal['pt']

Tensor framework. Only pt is supported.

'pt'

Returns:

Type Description
BatchEncoding

Batch encoding with prompt ids and optional forced token ids.

Raises:

Type Description
NotImplementedError

If the condition is unsupported by LayoutAction.

ValueError

If required condition payloads are missing.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
 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
def __call__(
    self,
    *,
    condition_type: ConditionType | str = ConditionType.unconditional,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput]
    | None = None,
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    num_elements: int | list[int] | Int[torch.Tensor, "batch"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    batch_size: int = 1,
    return_tensors: Literal["pt"] = "pt",
) -> BatchEncoding:
    """Encode a public generation condition.

    Args:
        condition_type: Canonical condition or supported release alias.
        bbox: Optional public boxes for completion prompts.
        labels: Optional labels for label/completion prompts.
        mask: Optional valid-element mask.
        num_elements: Optional element count or completion prefix length.
        box_format: Input box format.
        normalized: Whether boxes are normalized.
        canvas_size: Canvas size for pixel boxes.
        batch_size: Batch size for unconditional generation.
        return_tensors: Tensor framework. Only ``pt`` is supported.

    Returns:
        Batch encoding with prompt ids and optional forced token ids.

    Raises:
        NotImplementedError: If the condition is unsupported by LayoutAction.
        ValueError: If required condition payloads are missing.
    """
    if return_tensors != "pt":
        raise ValueError("LayoutActionProcessor only supports return_tensors='pt'")

    condition = normalize_condition_type(condition_type)
    if condition not in SUPPORTED_CONDITIONS:
        raise NotImplementedError(f"LayoutAction does not support {condition}.")

    if condition is ConditionType.unconditional:
        input_ids = torch.full(
            (int(batch_size), 1), self.config.bos_token_id, dtype=torch.long
        )
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "max_new_tokens": self.config.max_token_length,
            }
        )
    if labels is None:
        raise ValueError(f"{condition} generation requires labels")

    if bbox is None:
        label_tensor = torch.as_tensor(labels)
        if label_tensor.ndim == 1:
            label_tensor = label_tensor.unsqueeze(0)
        mask_tensor = (
            torch.ones_like(label_tensor, dtype=torch.bool)
            if mask is None
            else torch.as_tensor(mask, dtype=torch.bool)
        )
        if mask_tensor.ndim == 1:
            mask_tensor = mask_tensor.unsqueeze(0)
        bbox_tensor = torch.zeros(
            (*label_tensor.shape, 4),
            dtype=torch.float32,
            device=label_tensor.device,
        )
    else:
        bbox_tensor, label_tensor, mask_tensor = prepare_layout_tensors(
            bbox=bbox,
            labels=self._labels_to_ids(labels),
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
            clamp_converted_normalized=True,
        )
    full = self.tokenizer.encode_layout(
        bbox=bbox_tensor,
        labels=label_tensor.long(),
        mask=mask_tensor.bool(),
    )
    if condition is ConditionType.label:
        input_ids = full[:, :1]
        forced = torch.full(
            (full.size(0), self.config.max_token_length),
            -100,
            dtype=torch.long,
            device=full.device,
        )
        for step in range(
            0,
            self.config.max_elements * self.config.element_token_width,
            self.config.element_token_width,
        ):
            source_index = step + 1
            if source_index < full.size(1):
                forced[:, step] = full[:, source_index]
        return BatchEncoding(
            {
                "input_ids": input_ids,
                "attention_mask": torch.ones_like(input_ids),
                "forced_token_ids": forced,
                "max_new_tokens": self.config.max_token_length,
            }
        )
    prefix_elements = self._prefix_elements(num_elements, mask_tensor)
    prefix_length = 1 + prefix_elements * self.config.element_token_width
    input_ids = full[:, :prefix_length]
    remaining = max(0, self.config.max_token_length + 1 - input_ids.size(1))
    return BatchEncoding(
        {
            "input_ids": input_ids,
            "attention_mask": torch.ones_like(input_ids),
            "max_new_tokens": remaining,
        }
    )

post_process_layouts

post_process_layouts(
    sequences: Int[Tensor, "batch tokens"],
    *,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | LayoutActionOutputDict

Decode generated sequences to the common output schema.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
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
def post_process_layouts(
    self,
    sequences: Int[torch.Tensor, "batch tokens"],
    *,
    output_type: OutputType = "dataclass",
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | LayoutActionOutputDict:
    """Decode generated sequences to the common output schema."""
    decoded = self.tokenizer.decode_action_tokens(
        sequences.detach().cpu(),
        return_actions=return_intermediates,
    )
    intermediates = None
    if return_intermediates:
        intermediates = {"actions": decoded.get("actions")}
    output = LayoutGenerationOutput(
        bbox=cast(Float[torch.Tensor, "batch elements 4"], decoded["bbox"]),
        labels=cast(Int[torch.Tensor, "batch elements"], decoded["labels"]),
        mask=cast(Bool[torch.Tensor, "batch elements"], decoded["mask"]),
        id2label=dict(cast(dict[int, str], self.config.id2label)),
        sequences=sequences.detach().cpu(),
        intermediates=intermediates,
    )
    if output_type == "dict":
        return cast(LayoutActionOutputDict, dict(output))
    if output_type == "dataclass":
        return output
    raise ValueError(f"Unsupported output_type: {output_type}")

save_pretrained

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

Save processor and tokenizer metadata.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
233
234
235
236
237
238
239
240
241
242
243
244
245
def save_pretrained(
    self,
    save_directory: str | PathLike[str],
    push_to_hub: bool = False,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Save processor and tokenizer metadata."""
    _ = (push_to_hub, kwargs)
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    self.tokenizer.save_pretrained(out_dir)
    with (out_dir / PROCESSOR_CONFIG_FILE).open("w", encoding="utf-8") as f:
        json.dump({"processor_class": self.__class__.__name__}, f, indent=2)

from_pretrained classmethod

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

Load processor metadata from a checkpoint directory or Hub repo id.

Source code in models/layout-action/src/layout_action/processing_layout_action.py
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutActionProcessor":
    """Load processor metadata from a checkpoint directory or Hub repo id."""
    tokenizer = LayoutActionTokenizer.from_pretrained(
        pretrained_model_name_or_path,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        **kwargs,
    )
    return cls(tokenizer=tokenizer)

tokenization_layout_action

Synthetic action-token tokenizer for LayoutAction.

DecodedActions

Bases: TypedDict

Decoded action-token details.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
20
21
22
23
24
25
class DecodedActions(TypedDict):
    """Decoded action-token details."""

    option: Int[torch.Tensor, "batch elements 4"]
    object: Int[torch.Tensor, "batch elements 4"]
    value: Int[torch.Tensor, "batch elements 4"]

LayoutActionTokenizer

Bases: PreTrainedTokenizer

PreTrainedTokenizer for LayoutAction's 13-token element grammar.

Parameters:

Name Type Description Default
config LayoutActionConfig | None

LayoutAction config carrying vocabulary metadata.

None
tokenizer_config_file str | None

Optional saved tokenizer metadata path.

None
kwargs str | int | float | bool | None

Standard tokenizer keyword arguments.

{}

Examples:

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

    Args:
        config: LayoutAction config carrying vocabulary metadata.
        tokenizer_config_file: Optional saved tokenizer metadata path.
        kwargs: Standard tokenizer keyword arguments.

    Examples:
        >>> tokenizer = LayoutActionTokenizer(LayoutActionConfig(max_elements=2))
        >>> tokenizer.bos_token_id == tokenizer.config.bos_token_id
        True
    """

    model_input_names = ["input_ids", "attention_mask"]
    vocab_files_names = {"tokenizer_config_file": TOKENIZER_CONFIG_FILE}

    def __init__(
        self,
        config: LayoutActionConfig | None = None,
        tokenizer_config_file: str | None = None,
        **kwargs: str | int | float | bool | None,
    ) -> None:
        """Initialize synthetic token strings."""
        if config is None and tokenizer_config_file is not None:
            with Path(tokenizer_config_file).open(encoding="utf-8") as f:
                config = LayoutActionConfig(**json.load(f)["config"])
        if config is None:
            raise ValueError("LayoutActionTokenizer requires an explicit config")

        self.config = config
        self._token2id = self._build_vocab()
        self._id2token = {idx: token for token, idx in self._token2id.items()}
        kwargs.setdefault("bos_token", "[BOS]")
        kwargs.setdefault("eos_token", "[EOS]")
        kwargs.setdefault("pad_token", "[PAD]")
        kwargs.setdefault("unk_token", "[UNK]")
        kwargs.setdefault("model_max_length", self.config.max_token_length + 1)
        kwargs.setdefault("clean_up_tokenization_spaces", False)
        super().__init__(**kwargs)

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

    def _build_vocab(self) -> dict[str, int]:
        vocab = {f"value:{idx}": idx for idx in range(self.config.size)}
        vocab["[NO_VALUE]"] = self.config.no_value_token_id
        id2label = cast(dict[int, str], self.config.id2label)
        for label_id, label in id2label.items():
            vocab[f"label:{label_id}:{label}"] = self.config.label_token_id(
                int(label_id)
            )
        vocab["[COPY]"] = self.config.copy_token_id
        vocab["[MARGIN]"] = self.config.margin_token_id
        vocab["[GENERATE]"] = self.config.generate_token_id
        vocab["[NO_OBJ]"] = self.config.no_obj_token_id
        for idx in range(1, self.config.max_elements + 1):
            vocab[f"obj:{idx}"] = self.config.object_token_id(idx)
        vocab["[BOS]"] = self.config.bos_token_id
        vocab["[EOS]"] = self.config.eos_token_id
        vocab["[PAD]"] = self.config.pad_token_id
        vocab["[UNK]"] = self.config.pad_token_id
        return vocab

    def get_vocab(self) -> dict[str, int]:
        """Return synthetic token strings mapped to ids."""
        return dict(self._token2id)

    def _tokenize(
        self, text: str, **kwargs: str | int | float | bool | None
    ) -> list[str]:
        _ = kwargs
        return text.strip().split()

    def _convert_token_to_id(self, token: str) -> int:
        return self._token2id.get(token, self.config.pad_token_id)

    def _convert_id_to_token(self, index: int) -> str:
        return self._id2token.get(int(index), "[UNK]")

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

    def save_vocabulary(
        self, save_directory: str | PathLike[str], filename_prefix: str | None = None
    ) -> tuple[str, ...]:
        """Save tokenizer metadata."""
        out_dir = Path(save_directory)
        out_dir.mkdir(parents=True, exist_ok=True)
        name = (
            TOKENIZER_CONFIG_FILE
            if filename_prefix is None
            else f"{filename_prefix}-{TOKENIZER_CONFIG_FILE}"
        )
        path = out_dir / name
        with path.open("w", encoding="utf-8") as f:
            json.dump({"config": self.config.to_dict()}, f, indent=2, sort_keys=True)
        return (str(path),)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | PathLike[str],
        *inputs: str,
        cache_dir: str | PathLike[str] | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
        **kwargs: str | int | float | bool | None,
    ) -> "LayoutActionTokenizer":
        """Load tokenizer metadata through the standard Transformers resolver.

        Args:
            pretrained_model_name_or_path: Local tokenizer directory or Hub repo id.
            inputs: Reserved tokenizer inputs.
            cache_dir: Cache directory for Hub-backed files.
            force_download: Whether to refresh cached files.
            local_files_only: Whether to disable network resolution.
            token: Hugging Face token.
            revision: Hub revision.
            kwargs: Standard tokenizer keyword arguments.

        Returns:
            Loaded LayoutAction tokenizer.
        """
        _ = inputs
        subfolder = str(kwargs.pop("subfolder", ""))
        metadata = cached_file(
            pretrained_model_name_or_path,
            TOKENIZER_CONFIG_FILE,
            cache_dir=cache_dir,
            force_download=force_download,
            local_files_only=local_files_only,
            token=token,
            revision=revision,
            subfolder=subfolder,
        )
        if metadata is None:
            raise FileNotFoundError(
                f"Could not resolve {TOKENIZER_CONFIG_FILE} from "
                f"{pretrained_model_name_or_path!s}"
            )

        with Path(metadata).open(encoding="utf-8") as f:
            config = LayoutActionConfig(**json.load(f)["config"])
        return cls(config=config)

    def quantize_bbox(
        self, bbox: Float[torch.Tensor, "... 4"]
    ) -> Int[torch.Tensor, "... 4"]:
        """Quantize normalized center ``xywh`` boxes with checkpoint binning."""
        return (
            bbox.clamp(0.0, 1.0)
            .mul(self.config.size - 1)
            .round()
            .long()
            .clamp(0, self.config.size - 1)
        )

    def continuize_bbox(
        self, quantized_bbox: Int[torch.Tensor, "... 4"]
    ) -> Float[torch.Tensor, "... 4"]:
        """Decode quantized boxes to normalized center ``xywh`` values."""
        return quantized_bbox.float().clamp(0, self.config.size - 1) / (
            self.config.size - 1
        )

    def encode_layout(
        self,
        *,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Encode public normalized layouts to padded action-token sequences.

        Args:
            bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
            labels: Dataset-local labels shaped ``(B, E)``.
            mask: Valid-element mask shaped ``(B, E)``.

        Returns:
            Token ids shaped ``(B, max_token_length + 1)``.
        """
        quantized_bbox = self.quantize_bbox(bbox)
        return self.encode_action_layout(
            quantized_bbox=quantized_bbox,
            labels=labels.long(),
            mask=mask.bool(),
        )

    def encode_action_layout(
        self,
        *,
        quantized_bbox: Int[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"],
    ) -> Int[torch.Tensor, "batch tokens"]:
        """Encode already quantized boxes to action tokens."""
        batch = quantized_bbox.shape[0]
        sequences = torch.full(
            (batch, self.config.max_token_length + 1),
            self.config.pad_token_id,
            dtype=torch.long,
            device=quantized_bbox.device,
        )
        sequences[:, 0] = self.config.bos_token_id

        for batch_idx in range(batch):
            cursor = 1
            valid = torch.nonzero(mask[batch_idx], as_tuple=False).flatten()
            encoded_boxes: list[Int[torch.Tensor, "4"]] = []

            for elem_idx in valid[: self.config.max_elements]:
                label_id = int(labels[batch_idx, elem_idx].item())
                qbox = quantized_bbox[batch_idx, elem_idx].long()
                sequences[batch_idx, cursor] = self.config.label_token_id(label_id)
                cursor += 1
                triples = self._encode_action_triples(qbox, encoded_boxes)
                for geo_idx, (option_id, object_id, value_id) in enumerate(triples):
                    _ = geo_idx
                    sequences[batch_idx, cursor] = option_id
                    sequences[batch_idx, cursor + 1] = object_id
                    sequences[batch_idx, cursor + 2] = value_id
                    cursor += 3
                encoded_boxes.append(qbox.detach().clone())

            sequences[batch_idx, cursor] = self.config.eos_token_id
        return sequences

    def _encode_action_triples(
        self, qbox: Int[torch.Tensor, "4"], previous_boxes: list[Int[torch.Tensor, "4"]]
    ) -> list[tuple[int, int, int]]:
        """Encode one quantized box with checkpoint copy/margin/generate precedence."""
        if not previous_boxes:
            return [
                (
                    self.config.generate_token_id,
                    self.config.no_obj_token_id,
                    int(qbox[geo_idx].item()),
                )
                for geo_idx in range(4)
            ]
        previous = torch.stack(previous_boxes).to(device=qbox.device, dtype=torch.long)
        current = qbox.to(device=previous.device, dtype=torch.long)
        copy_label = previous.eq(current.unsqueeze(0))
        copy_choice = copy_label.any(dim=0)
        margin_label = torch.zeros(
            (previous.size(0), 2), dtype=torch.bool, device=previous.device
        )
        margin_label[:, 0] = previous[:, 1].eq(current[1])
        margin_label[:, 1] = previous[:, 0].eq(current[0])
        margin_value = (
            current[:2].float().unsqueeze(0)
            - previous[:, :2].float()
            - 0.5 * previous[:, 2:].float()
            - 0.5 * current[2:].float().unsqueeze(0)
        )
        margin_label &= margin_value.ge(0)
        margin_label_x4 = torch.cat(
            [margin_label, torch.zeros_like(margin_label)], dim=1
        )
        margin_choice = margin_label_x4.any(dim=0) & ~copy_choice
        generate_choice = ~(copy_choice | margin_choice)
        triples: list[tuple[int, int, int]] = []
        for geo_idx in range(4):
            if bool(copy_choice[geo_idx].item()):
                ref = self._latest_back_reference(copy_label[:, geo_idx])
                triples.append(
                    (
                        self.config.copy_token_id,
                        self.config.object_token_id(ref),
                        self.config.no_value_token_id,
                    )
                )
            elif bool(margin_choice[geo_idx].item()):
                ref = self._latest_back_reference(margin_label_x4[:, geo_idx])
                value = int(round(float(margin_value[-ref, geo_idx].item())))
                triples.append(
                    (
                        self.config.margin_token_id,
                        self.config.object_token_id(ref),
                        max(0, min(self.config.size - 1, value)),
                    )
                )
            elif bool(generate_choice[geo_idx].item()):
                triples.append(
                    (
                        self.config.generate_token_id,
                        self.config.no_obj_token_id,
                        int(current[geo_idx].item()),
                    )
                )
            else:
                raise ValueError("LayoutAction action selection produced no option")

        return triples

    def _latest_back_reference(self, hits: Bool[torch.Tensor, "elements"]) -> int:
        hit_indices = torch.nonzero(hits, as_tuple=False).flatten()
        if hit_indices.numel() == 0:
            raise ValueError("Expected at least one back-reference hit")

        return int(hits.numel() - hit_indices[-1].item())

    def decode_layout(
        self, input_ids: Int[torch.Tensor, "batch_or_tokens ..."]
    ) -> dict[str, Shaped[torch.Tensor, "..."]]:
        """Decode action-token sequences to public layout tensors."""
        return cast(
            dict[str, Shaped[torch.Tensor, "..."]],
            self.decode_action_tokens(input_ids, return_actions=False),
        )

    def decode_action_tokens(
        self,
        input_ids: Int[torch.Tensor, "batch_or_tokens ..."],
        *,
        return_actions: bool = False,
    ) -> dict[
        str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
    ]:
        """Decode action tokens and optionally return raw action details."""
        ids = input_ids.long()
        if ids.ndim == 1:
            ids = ids.unsqueeze(0)
        batch = ids.shape[0]
        bbox = torch.zeros(batch, self.config.max_elements, 4, dtype=torch.float32)
        labels = torch.zeros(batch, self.config.max_elements, dtype=torch.long)
        mask = torch.zeros(batch, self.config.max_elements, dtype=torch.bool)
        option = torch.full((batch, self.config.max_elements, 4), -1, dtype=torch.long)
        obj = torch.full_like(option, -1)
        value = torch.full_like(option, -1)
        for batch_idx in range(batch):
            tokens = self._trim_special_tokens(ids[batch_idx])
            usable = tokens[
                : (tokens.numel() // self.config.element_token_width)
                * self.config.element_token_width
            ]

            boxes: list[Int[torch.Tensor, "4"]] = []
            out_idx = 0
            for start in range(0, usable.numel(), self.config.element_token_width):
                if out_idx >= self.config.max_elements:
                    break
                element = usable[start : start + self.config.element_token_width]
                label_id = self.config.label_id_from_token(int(element[0]))
                if label_id is None:
                    continue
                qbox = torch.zeros(4, dtype=torch.long)
                valid = True
                deferred_margins: list[tuple[int, int, int]] = []

                for geo_idx in range(4):
                    triple = element[1 + geo_idx * 3 : 1 + (geo_idx + 1) * 3]
                    opt_id = int(triple[0])
                    obj_id = int(triple[1])
                    val_id = int(triple[2])
                    option[batch_idx, out_idx, geo_idx] = opt_id
                    obj[batch_idx, out_idx, geo_idx] = obj_id
                    value[batch_idx, out_idx, geo_idx] = val_id
                    if (
                        opt_id == self.config.generate_token_id
                        and 0 <= val_id < self.config.size
                    ):
                        qbox[geo_idx] = val_id
                    elif opt_id == self.config.copy_token_id:
                        ref = self.config.back_reference_from_token(obj_id)
                        if ref is None or ref > len(boxes):
                            valid = False
                            break
                        qbox[geo_idx] = boxes[-ref][geo_idx].long()
                    elif opt_id == self.config.margin_token_id and geo_idx < 2:
                        ref = self.config.back_reference_from_token(obj_id)
                        if (
                            ref is None
                            or ref > len(boxes)
                            or not (0 <= val_id < self.config.size)
                        ):
                            valid = False
                            break
                        deferred_margins.append((geo_idx, ref, val_id))
                    else:
                        valid = False
                        break
                if not valid:
                    continue

                for geo_idx, ref, val_id in deferred_margins:
                    base = self.continuize_bbox(boxes[-ref].unsqueeze(0))[0]
                    cur = self.continuize_bbox(qbox.unsqueeze(0))[0]
                    margin = float(val_id) / (self.config.size - 1)
                    coord = (
                        base[geo_idx]
                        + 0.5 * base[geo_idx + 2]
                        + 0.5 * cur[geo_idx + 2]
                        + margin
                    )
                    qbox[geo_idx] = int(
                        round(float(coord.item()) * (self.config.size - 1))
                    )

                boxes.append(qbox.clone())
                bbox[batch_idx, out_idx] = self.continuize_bbox(qbox)
                labels[batch_idx, out_idx] = label_id
                mask[batch_idx, out_idx] = True
                out_idx += 1
        result: dict[
            str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
        ] = {
            "bbox": bbox.clamp(0.0, 1.0),
            "labels": labels,
            "mask": mask,
        }
        if return_actions:
            result["actions"] = {"option": option, "object": obj, "value": value}
        return result

    def _trim_special_tokens(
        self, input_ids: Int[torch.Tensor, "tokens"]
    ) -> Int[torch.Tensor, "tokens"]:
        tokens = input_ids.detach().cpu()
        bos = torch.nonzero(tokens == self.config.bos_token_id, as_tuple=False)
        if bos.numel() > 0:
            tokens = tokens[int(bos[0].item()) + 1 :]
        eos = torch.nonzero(tokens == self.config.eos_token_id, as_tuple=False)
        if eos.numel() > 0:
            tokens = tokens[: int(eos[0].item())]
        return tokens[tokens != self.config.pad_token_id]

vocab_size property

vocab_size: int

Return the LayoutAction vocabulary size.

__init__

__init__(
    config: LayoutActionConfig | None = None,
    tokenizer_config_file: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None

Initialize synthetic token strings.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def __init__(
    self,
    config: LayoutActionConfig | None = None,
    tokenizer_config_file: str | None = None,
    **kwargs: str | int | float | bool | None,
) -> None:
    """Initialize synthetic token strings."""
    if config is None and tokenizer_config_file is not None:
        with Path(tokenizer_config_file).open(encoding="utf-8") as f:
            config = LayoutActionConfig(**json.load(f)["config"])
    if config is None:
        raise ValueError("LayoutActionTokenizer requires an explicit config")

    self.config = config
    self._token2id = self._build_vocab()
    self._id2token = {idx: token for token, idx in self._token2id.items()}
    kwargs.setdefault("bos_token", "[BOS]")
    kwargs.setdefault("eos_token", "[EOS]")
    kwargs.setdefault("pad_token", "[PAD]")
    kwargs.setdefault("unk_token", "[UNK]")
    kwargs.setdefault("model_max_length", self.config.max_token_length + 1)
    kwargs.setdefault("clean_up_tokenization_spaces", False)
    super().__init__(**kwargs)

get_vocab

get_vocab() -> dict[str, int]

Return synthetic token strings mapped to ids.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
94
95
96
def get_vocab(self) -> dict[str, int]:
    """Return synthetic token strings mapped to ids."""
    return dict(self._token2id)

convert_tokens_to_string

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

Join synthetic layout tokens.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
110
111
112
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join synthetic layout tokens."""
    return " ".join(tokens)

save_vocabulary

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

Save tokenizer metadata.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def save_vocabulary(
    self, save_directory: str | PathLike[str], filename_prefix: str | None = None
) -> tuple[str, ...]:
    """Save tokenizer metadata."""
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    name = (
        TOKENIZER_CONFIG_FILE
        if filename_prefix is None
        else f"{filename_prefix}-{TOKENIZER_CONFIG_FILE}"
    )
    path = out_dir / name
    with path.open("w", encoding="utf-8") as f:
        json.dump({"config": self.config.to_dict()}, f, indent=2, sort_keys=True)
    return (str(path),)

from_pretrained classmethod

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

Load tokenizer metadata through the standard Transformers resolver.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | PathLike[str]

Local tokenizer directory or Hub repo id.

required
inputs str

Reserved tokenizer inputs.

()
cache_dir str | PathLike[str] | None

Cache directory for Hub-backed files.

None
force_download bool

Whether to refresh cached files.

False
local_files_only bool

Whether to disable network resolution.

False
token str | bool | None

Hugging Face token.

None
revision str

Hub revision.

'main'
kwargs str | int | float | bool | None

Standard tokenizer keyword arguments.

{}

Returns:

Type Description
'LayoutActionTokenizer'

Loaded LayoutAction tokenizer.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | PathLike[str],
    *inputs: str,
    cache_dir: str | PathLike[str] | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
    **kwargs: str | int | float | bool | None,
) -> "LayoutActionTokenizer":
    """Load tokenizer metadata through the standard Transformers resolver.

    Args:
        pretrained_model_name_or_path: Local tokenizer directory or Hub repo id.
        inputs: Reserved tokenizer inputs.
        cache_dir: Cache directory for Hub-backed files.
        force_download: Whether to refresh cached files.
        local_files_only: Whether to disable network resolution.
        token: Hugging Face token.
        revision: Hub revision.
        kwargs: Standard tokenizer keyword arguments.

    Returns:
        Loaded LayoutAction tokenizer.
    """
    _ = inputs
    subfolder = str(kwargs.pop("subfolder", ""))
    metadata = cached_file(
        pretrained_model_name_or_path,
        TOKENIZER_CONFIG_FILE,
        cache_dir=cache_dir,
        force_download=force_download,
        local_files_only=local_files_only,
        token=token,
        revision=revision,
        subfolder=subfolder,
    )
    if metadata is None:
        raise FileNotFoundError(
            f"Could not resolve {TOKENIZER_CONFIG_FILE} from "
            f"{pretrained_model_name_or_path!s}"
        )

    with Path(metadata).open(encoding="utf-8") as f:
        config = LayoutActionConfig(**json.load(f)["config"])
    return cls(config=config)

quantize_bbox

quantize_bbox(
    bbox: Float[Tensor, "... 4"],
) -> Int[torch.Tensor, "... 4"]

Quantize normalized center xywh boxes with checkpoint binning.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
179
180
181
182
183
184
185
186
187
188
189
def quantize_bbox(
    self, bbox: Float[torch.Tensor, "... 4"]
) -> Int[torch.Tensor, "... 4"]:
    """Quantize normalized center ``xywh`` boxes with checkpoint binning."""
    return (
        bbox.clamp(0.0, 1.0)
        .mul(self.config.size - 1)
        .round()
        .long()
        .clamp(0, self.config.size - 1)
    )

continuize_bbox

continuize_bbox(
    quantized_bbox: Int[Tensor, "... 4"],
) -> Float[torch.Tensor, "... 4"]

Decode quantized boxes to normalized center xywh values.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
191
192
193
194
195
196
197
def continuize_bbox(
    self, quantized_bbox: Int[torch.Tensor, "... 4"]
) -> Float[torch.Tensor, "... 4"]:
    """Decode quantized boxes to normalized center ``xywh`` values."""
    return quantized_bbox.float().clamp(0, self.config.size - 1) / (
        self.config.size - 1
    )

encode_layout

encode_layout(
    *,
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]

Encode public normalized layouts to padded action-token sequences.

Parameters:

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

Normalized center xywh boxes shaped (B, E, 4).

required
labels Int[Tensor, 'batch elements']

Dataset-local labels shaped (B, E).

required
mask Bool[Tensor, 'batch elements']

Valid-element mask shaped (B, E).

required

Returns:

Type Description
Int[Tensor, 'batch tokens']

Token ids shaped (B, max_token_length + 1).

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def encode_layout(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Encode public normalized layouts to padded action-token sequences.

    Args:
        bbox: Normalized center ``xywh`` boxes shaped ``(B, E, 4)``.
        labels: Dataset-local labels shaped ``(B, E)``.
        mask: Valid-element mask shaped ``(B, E)``.

    Returns:
        Token ids shaped ``(B, max_token_length + 1)``.
    """
    quantized_bbox = self.quantize_bbox(bbox)
    return self.encode_action_layout(
        quantized_bbox=quantized_bbox,
        labels=labels.long(),
        mask=mask.bool(),
    )

encode_action_layout

encode_action_layout(
    *,
    quantized_bbox: Int[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]

Encode already quantized boxes to action tokens.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
def encode_action_layout(
    self,
    *,
    quantized_bbox: Int[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"],
) -> Int[torch.Tensor, "batch tokens"]:
    """Encode already quantized boxes to action tokens."""
    batch = quantized_bbox.shape[0]
    sequences = torch.full(
        (batch, self.config.max_token_length + 1),
        self.config.pad_token_id,
        dtype=torch.long,
        device=quantized_bbox.device,
    )
    sequences[:, 0] = self.config.bos_token_id

    for batch_idx in range(batch):
        cursor = 1
        valid = torch.nonzero(mask[batch_idx], as_tuple=False).flatten()
        encoded_boxes: list[Int[torch.Tensor, "4"]] = []

        for elem_idx in valid[: self.config.max_elements]:
            label_id = int(labels[batch_idx, elem_idx].item())
            qbox = quantized_bbox[batch_idx, elem_idx].long()
            sequences[batch_idx, cursor] = self.config.label_token_id(label_id)
            cursor += 1
            triples = self._encode_action_triples(qbox, encoded_boxes)
            for geo_idx, (option_id, object_id, value_id) in enumerate(triples):
                _ = geo_idx
                sequences[batch_idx, cursor] = option_id
                sequences[batch_idx, cursor + 1] = object_id
                sequences[batch_idx, cursor + 2] = value_id
                cursor += 3
            encoded_boxes.append(qbox.detach().clone())

        sequences[batch_idx, cursor] = self.config.eos_token_id
    return sequences

decode_layout

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

Decode action-token sequences to public layout tensors.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
337
338
339
340
341
342
343
344
def decode_layout(
    self, input_ids: Int[torch.Tensor, "batch_or_tokens ..."]
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Decode action-token sequences to public layout tensors."""
    return cast(
        dict[str, Shaped[torch.Tensor, "..."]],
        self.decode_action_tokens(input_ids, return_actions=False),
    )

decode_action_tokens

decode_action_tokens(
    input_ids: Int[Tensor, "batch_or_tokens ..."],
    *,
    return_actions: bool = False,
) -> dict[
    str,
    Shaped[torch.Tensor, "..."]
    | dict[str, Shaped[torch.Tensor, "..."]],
]

Decode action tokens and optionally return raw action details.

Source code in models/layout-action/src/layout_action/tokenization_layout_action.py
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
def decode_action_tokens(
    self,
    input_ids: Int[torch.Tensor, "batch_or_tokens ..."],
    *,
    return_actions: bool = False,
) -> dict[
    str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
]:
    """Decode action tokens and optionally return raw action details."""
    ids = input_ids.long()
    if ids.ndim == 1:
        ids = ids.unsqueeze(0)
    batch = ids.shape[0]
    bbox = torch.zeros(batch, self.config.max_elements, 4, dtype=torch.float32)
    labels = torch.zeros(batch, self.config.max_elements, dtype=torch.long)
    mask = torch.zeros(batch, self.config.max_elements, dtype=torch.bool)
    option = torch.full((batch, self.config.max_elements, 4), -1, dtype=torch.long)
    obj = torch.full_like(option, -1)
    value = torch.full_like(option, -1)
    for batch_idx in range(batch):
        tokens = self._trim_special_tokens(ids[batch_idx])
        usable = tokens[
            : (tokens.numel() // self.config.element_token_width)
            * self.config.element_token_width
        ]

        boxes: list[Int[torch.Tensor, "4"]] = []
        out_idx = 0
        for start in range(0, usable.numel(), self.config.element_token_width):
            if out_idx >= self.config.max_elements:
                break
            element = usable[start : start + self.config.element_token_width]
            label_id = self.config.label_id_from_token(int(element[0]))
            if label_id is None:
                continue
            qbox = torch.zeros(4, dtype=torch.long)
            valid = True
            deferred_margins: list[tuple[int, int, int]] = []

            for geo_idx in range(4):
                triple = element[1 + geo_idx * 3 : 1 + (geo_idx + 1) * 3]
                opt_id = int(triple[0])
                obj_id = int(triple[1])
                val_id = int(triple[2])
                option[batch_idx, out_idx, geo_idx] = opt_id
                obj[batch_idx, out_idx, geo_idx] = obj_id
                value[batch_idx, out_idx, geo_idx] = val_id
                if (
                    opt_id == self.config.generate_token_id
                    and 0 <= val_id < self.config.size
                ):
                    qbox[geo_idx] = val_id
                elif opt_id == self.config.copy_token_id:
                    ref = self.config.back_reference_from_token(obj_id)
                    if ref is None or ref > len(boxes):
                        valid = False
                        break
                    qbox[geo_idx] = boxes[-ref][geo_idx].long()
                elif opt_id == self.config.margin_token_id and geo_idx < 2:
                    ref = self.config.back_reference_from_token(obj_id)
                    if (
                        ref is None
                        or ref > len(boxes)
                        or not (0 <= val_id < self.config.size)
                    ):
                        valid = False
                        break
                    deferred_margins.append((geo_idx, ref, val_id))
                else:
                    valid = False
                    break
            if not valid:
                continue

            for geo_idx, ref, val_id in deferred_margins:
                base = self.continuize_bbox(boxes[-ref].unsqueeze(0))[0]
                cur = self.continuize_bbox(qbox.unsqueeze(0))[0]
                margin = float(val_id) / (self.config.size - 1)
                coord = (
                    base[geo_idx]
                    + 0.5 * base[geo_idx + 2]
                    + 0.5 * cur[geo_idx + 2]
                    + margin
                )
                qbox[geo_idx] = int(
                    round(float(coord.item()) * (self.config.size - 1))
                )

            boxes.append(qbox.clone())
            bbox[batch_idx, out_idx] = self.continuize_bbox(qbox)
            labels[batch_idx, out_idx] = label_id
            mask[batch_idx, out_idx] = True
            out_idx += 1
    result: dict[
        str, Shaped[torch.Tensor, "..."] | dict[str, Shaped[torch.Tensor, "..."]]
    ] = {
        "bbox": bbox.clamp(0.0, 1.0),
        "labels": labels,
        "mask": mask,
    }
    if return_actions:
        result["actions"] = {"option": option, "object": obj, "value": value}
    return result