Skip to content

Lace

Diffusers-style LACE layout generation package.

LaceDatasetSpec dataclass

Static dataset metadata used to configure a LACE checkpoint.

Attributes:

Name Type Description
dataset DatasetName

Canonical dataset name.

labels tuple[LaceLabel, ...]

Ordered category labels without the padding class.

max_seq_length int

Maximum number of layout elements.

dim_transformer int

Transformer hidden size used by the original model.

nhead int

Number of attention heads.

num_layers int

Number of transformer blocks.

dim_feedforward int

Feed-forward hidden size.

Source code in models/lace/src/lace/configuration_lace.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@dataclass(frozen=True)
class LaceDatasetSpec:
    """Static dataset metadata used to configure a LACE checkpoint.

    Attributes:
        dataset: Canonical dataset name.
        labels: Ordered category labels without the padding class.
        max_seq_length: Maximum number of layout elements.
        dim_transformer: Transformer hidden size used by the original model.
        nhead: Number of attention heads.
        num_layers: Number of transformer blocks.
        dim_feedforward: Feed-forward hidden size.
    """

    dataset: DatasetName
    labels: tuple[LaceLabel, ...]
    max_seq_length: int = 25
    dim_transformer: int = 512
    nhead: int = 16
    num_layers: int = 4
    dim_feedforward: int = 2048

    @property
    def pad_label_id(self) -> int:
        """Return the integer id reserved for padding."""
        return len(self.labels)

    @property
    def num_classes_with_pad(self) -> int:
        """Return the number of category channels including padding."""
        return len(self.labels) + 1

    @property
    def seq_dim(self) -> int:
        """Return the latent per-element feature size."""
        return self.num_classes_with_pad + 4

    @property
    def id2label(self) -> dict[int, str]:
        """Return the category id to label mapping."""
        return dict(enumerate(str(label) for label in self.labels))

pad_label_id property

pad_label_id: int

Return the integer id reserved for padding.

num_classes_with_pad property

num_classes_with_pad: int

Return the number of category channels including padding.

seq_dim property

seq_dim: int

Return the latent per-element feature size.

id2label property

id2label: dict[int, str]

Return the category id to label mapping.

ActivationName

Bases: StrEnum

Supported feed-forward activation names.

Origin

gelu2 is the VQ-Diffusion GELU2/QuickGELU branch used by the LayoutDM, LACE, and LayoutFlow transformer utilities.

Source code in lib/laygen/src/laygen/nn/activations.py
18
19
20
21
22
23
24
25
26
27
28
class ActivationName(StrEnum):
    """Supported feed-forward activation names.

    Origin:
        ``gelu2`` is the VQ-Diffusion GELU2/QuickGELU branch used by the
        LayoutDM, LACE, and LayoutFlow transformer utilities.
    """

    relu = auto()
    gelu = auto()
    gelu2 = auto()

LaceModelOutput dataclass

Bases: BaseOutput

Output returned by the LACE transformer.

Attributes:

Name Type Description
sample Float[Tensor, 'batch elements channels']

Predicted noise tensor with the same shape as the input sample.

Source code in models/lace/src/lace/modeling_lace.py
46
47
48
49
50
51
52
53
54
@dataclass
class LaceModelOutput(BaseOutput):
    """Output returned by the LACE transformer.

    Attributes:
        sample: Predicted noise tensor with the same shape as the input sample.
    """

    sample: Float[torch.Tensor, "batch elements channels"]

LaceTransformerModel

Bases: ModelMixin, ConfigMixin

Transformer denoiser for continuous LACE layout tensors.

Parameters:

Name Type Description Default
seq_dim int

Number of channels per layout element.

required
max_seq_length int

Maximum number of layout elements.

25
num_layers int

Number of transformer blocks.

4
dim_transformer int

Hidden dimension.

512
nhead int

Number of attention heads.

16
dim_feedforward int

Feed-forward hidden dimension.

2048
diffusion_step int

Maximum diffusion timestep.

1000
timestep_type TimestepEmbeddingType | str | None

Timestep-conditioned normalization variant.

adalayernorm
Source code in models/lace/src/lace/modeling_lace.py
 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
class LaceTransformerModel(ModelMixin, ConfigMixin):
    """Transformer denoiser for continuous LACE layout tensors.

    Args:
        seq_dim: Number of channels per layout element.
        max_seq_length: Maximum number of layout elements.
        num_layers: Number of transformer blocks.
        dim_transformer: Hidden dimension.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden dimension.
        diffusion_step: Maximum diffusion timestep.
        timestep_type: Timestep-conditioned normalization variant.
    """

    config_name = "model_config.json"

    pos_embed: Float[torch.Tensor, "max_seq_length dim_transformer"]

    @register_to_config
    def __init__(
        self,
        *,
        seq_dim: int,
        max_seq_length: int = 25,
        num_layers: int = 4,
        dim_transformer: int = 512,
        nhead: int = 16,
        dim_feedforward: int = 2048,
        diffusion_step: int = 1000,
        timestep_type: TimestepEmbeddingType
        | str
        | None = TimestepEmbeddingType.adalayernorm,
    ) -> None:
        """Initialize a LACE transformer denoiser."""
        super().__init__()
        self.seq_dim = seq_dim
        self.max_seq_length = max_seq_length
        self.pos_encoder = SinusoidalPosEmb(max_seq_length, dim_transformer)
        pos_i = torch.arange(max_seq_length)
        self.register_buffer("pos_embed", self.pos_encoder(pos_i), persistent=False)
        self.layer_in = nn.Linear(seq_dim, dim_transformer)
        encoder_layer = Block(
            d_model=dim_transformer,
            nhead=nhead,
            dim_feedforward=dim_feedforward,
            diffusion_step=diffusion_step,
            timestep_type=timestep_type,
        )
        self.layers = clone_module_list(encoder_layer, num_layers)
        self.num_layers = num_layers
        self.layer_out = nn.Linear(dim_transformer, seq_dim)

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        attention_mask: Bool[torch.Tensor, "batch elements"] | None = None,
        return_dict: bool = True,
    ) -> LaceModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict denoising residuals for a layout sample.

        Args:
            sample: Noisy layout tensor.
            timestep: Diffusion timestep per sample.
            attention_mask: Optional valid-element mask.
            return_dict: Whether to return ``LaceModelOutput``.

        Returns:
            Output dataclass or a one-item tuple containing the prediction.
        """
        output = F.softplus(self.layer_in(sample))
        pos_i = torch.arange(output.shape[1], device=output.device)
        output = output + self.pos_encoder(pos_i).to(output)
        key_padding_mask = None if attention_mask is None else ~attention_mask.bool()
        for i, layer in enumerate(self.layers):
            output = layer(
                output,
                src_key_padding_mask=key_padding_mask,
                timestep=timestep,
            )
            if i < self.num_layers - 1:
                output = F.softplus(output)
        output = self.layer_out(output)
        if not return_dict:
            return (output,)
        return LaceModelOutput(sample=output)

__init__

__init__(
    *,
    seq_dim: int,
    max_seq_length: int = 25,
    num_layers: int = 4,
    dim_transformer: int = 512,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    diffusion_step: int = 1000,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None

Initialize a LACE transformer denoiser.

Source code in models/lace/src/lace/modeling_lace.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@register_to_config
def __init__(
    self,
    *,
    seq_dim: int,
    max_seq_length: int = 25,
    num_layers: int = 4,
    dim_transformer: int = 512,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    diffusion_step: int = 1000,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None:
    """Initialize a LACE transformer denoiser."""
    super().__init__()
    self.seq_dim = seq_dim
    self.max_seq_length = max_seq_length
    self.pos_encoder = SinusoidalPosEmb(max_seq_length, dim_transformer)
    pos_i = torch.arange(max_seq_length)
    self.register_buffer("pos_embed", self.pos_encoder(pos_i), persistent=False)
    self.layer_in = nn.Linear(seq_dim, dim_transformer)
    encoder_layer = Block(
        d_model=dim_transformer,
        nhead=nhead,
        dim_feedforward=dim_feedforward,
        diffusion_step=diffusion_step,
        timestep_type=timestep_type,
    )
    self.layers = clone_module_list(encoder_layer, num_layers)
    self.num_layers = num_layers
    self.layer_out = nn.Linear(dim_transformer, seq_dim)

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    attention_mask: Bool[Tensor, "batch elements"]
    | None = None,
    return_dict: bool = True,
) -> (
    LaceModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict denoising residuals for a layout sample.

Parameters:

Name Type Description Default
sample Float[Tensor, 'batch elements channels']

Noisy layout tensor.

required
timestep Int[Tensor, 'batch']

Diffusion timestep per sample.

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

Optional valid-element mask.

None
return_dict bool

Whether to return LaceModelOutput.

True

Returns:

Type Description
LaceModelOutput | tuple[Float[Tensor, 'batch elements channels']]

Output dataclass or a one-item tuple containing the prediction.

Source code in models/lace/src/lace/modeling_lace.py
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
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    attention_mask: Bool[torch.Tensor, "batch elements"] | None = None,
    return_dict: bool = True,
) -> LaceModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict denoising residuals for a layout sample.

    Args:
        sample: Noisy layout tensor.
        timestep: Diffusion timestep per sample.
        attention_mask: Optional valid-element mask.
        return_dict: Whether to return ``LaceModelOutput``.

    Returns:
        Output dataclass or a one-item tuple containing the prediction.
    """
    output = F.softplus(self.layer_in(sample))
    pos_i = torch.arange(output.shape[1], device=output.device)
    output = output + self.pos_encoder(pos_i).to(output)
    key_padding_mask = None if attention_mask is None else ~attention_mask.bool()
    for i, layer in enumerate(self.layers):
        output = layer(
            output,
            src_key_padding_mask=key_padding_mask,
            timestep=timestep,
        )
        if i < self.num_layers - 1:
            output = F.softplus(output)
    output = self.layer_out(output)
    if not return_dict:
        return (output,)
    return LaceModelOutput(sample=output)

TimestepEmbeddingType

Bases: StrEnum

Supported timestep-conditioned normalization variants.

Origin

These names come from VQ-Diffusion-derived adaptive normalization modes used by the LayoutDM and LACE checkpoint backbones.

Source code in lib/laygen/src/laygen/nn/embeddings.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class TimestepEmbeddingType(StrEnum):
    """Supported timestep-conditioned normalization variants.

    Origin:
        These names come from VQ-Diffusion-derived adaptive normalization modes
        used by the LayoutDM and LACE checkpoint backbones.
    """

    adalayernorm = auto()
    adainnorm = auto()
    adalayernorm_abs = auto()
    adainnorm_abs = auto()
    adalayernorm_mlp = auto()
    adainnorm_mlp = auto()

ConditionType

Bases: StrEnum

Canonical condition names used by layout generation interfaces.

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

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

LacePipeline

Bases: DiffusionPipeline

Generate layouts with a converted LACE checkpoint.

Parameters:

Name Type Description Default
model LaceTransformerModel

LACE transformer denoiser.

required
scheduler LaceScheduler

DDIM-style scheduler.

required
processor LaceProcessor

Processor that encodes and decodes layout tensors.

required

Examples:

>>> from lace import LaceProcessor, LaceScheduler, LaceTransformerModel
>>> model = LaceTransformerModel(seq_dim=10, max_seq_length=2, num_layers=1, dim_transformer=8, nhead=2, dim_feedforward=16)
>>> pipe = LacePipeline(model=model, scheduler=LaceScheduler(ddim_num_steps=1), processor=LaceProcessor.from_dataset("publaynet"))
>>> pipe.processor.max_seq_length
25
Source code in models/lace/src/lace/pipeline_lace.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
class LacePipeline(DiffusionPipeline):
    """Generate layouts with a converted LACE checkpoint.

    Args:
        model: LACE transformer denoiser.
        scheduler: DDIM-style scheduler.
        processor: Processor that encodes and decodes layout tensors.

    Examples:
        >>> from lace import LaceProcessor, LaceScheduler, LaceTransformerModel
        >>> model = LaceTransformerModel(seq_dim=10, max_seq_length=2, num_layers=1, dim_transformer=8, nhead=2, dim_feedforward=16)
        >>> pipe = LacePipeline(model=model, scheduler=LaceScheduler(ddim_num_steps=1), processor=LaceProcessor.from_dataset("publaynet"))
        >>> pipe.processor.max_seq_length
        25
    """

    model_cpu_offload_seq = "model"
    _optional_components = ["processor"]

    def __init__(
        self,
        model: LaceTransformerModel,
        scheduler: LaceScheduler,
        processor: LaceProcessor,
    ) -> None:
        """Attach the converted LACE denoiser, scheduler, and processor."""
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler)
        self.model = model
        self.scheduler = scheduler
        self.processor = processor
        self.model.eval()

    @property
    def components(
        self,
    ) -> dict[str, LaceTransformerModel | LaceScheduler | LaceProcessor]:
        """Expose modules and processor metadata for Diffusers serialization."""
        return dict(
            model=self.model, scheduler=self.scheduler, processor=self.processor
        )

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        num_inference_steps: int | None = None,
        generator: torch.Generator | None = None,
        seed: int | None = None,
        condition_type: ConditionType | str | None = ConditionType.unconditional,
        bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
        labels: Int[torch.Tensor, "batch elements"] | None = None,
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        completion_ratio: float = 0.2,
        refinement_noise: float = 0.1,
        beautify: bool = False,
        beautify_overlap_weight: float | None = None,
        beautify_alignment_weight: float = 1.0,
        output_type: PipelineOutputType | str = PipelineOutputType.dataclass,
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Run LACE denoising and return generated layouts.

        Args:
            batch_size: Number of layouts to generate for unconditional calls.
            num_inference_steps: Number of DDIM steps. Uses scheduler default if
                omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            seed: Convenience seed used only when ``generator`` is absent.
            condition_type: Conditioning mode or alias.
            bbox: Conditioning boxes for non-unconditional modes.
            labels: Conditioning labels for non-unconditional modes.
            mask: Optional conditioning mask.
            box_format: Input box format for conditioning boxes.
            normalized: Whether conditioning boxes are normalized.
            canvas_size: Pixel canvas size required when ``normalized`` is false.
            completion_ratio: Maximum random completion fraction.
            refinement_noise: Noise scale for refinement conditioning.
            beautify: Whether to run the aesthetic post-optimization.
            beautify_overlap_weight: Optional overlap penalty override.
            beautify_alignment_weight: Alignment penalty weight.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to return the denoising trajectory.

        Returns:
            Layout output dataclass or a dictionary.

        Raises:
            ValueError: If a condition/output mode is unsupported or required
                conditioning tensors are missing.
        """
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        encoded = None
        if canonical is not ConditionType.unconditional:
            if bbox is None or labels is None:
                raise ValueError(
                    f"bbox and labels are required for condition_type={condition_type}"
                )

            encoded = self.processor(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            batch_size = encoded[LACE_LAYOUT_KEY].shape[0]
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.processor.max_seq_length,
            self.processor.seq_dim,
            device=self.device,
            generator=generator,
        )
        if canonical is ConditionType.refinement:
            assert encoded is not None
            noise = torch.randn(
                encoded[LACE_BBOX_KEY].shape,
                dtype=encoded[LACE_BBOX_KEY].dtype,
                device=encoded[LACE_BBOX_KEY].device,
                generator=generator,
            )
            noisy_bbox = (encoded[LACE_BBOX_KEY] + refinement_noise * noise).clamp(0, 1)
            sample = self.processor.encode(
                noisy_bbox.to(self.device),
                encoded[LACE_LABELS_KEY].to(self.device),
                encoded[LACE_MASK_KEY].to(self.device),
            )
        real_layout = (
            None if encoded is None else encoded[LACE_LAYOUT_KEY].to(self.device)
        )
        fix_mask = self._build_fix_mask(
            canonical, real_layout, completion_ratio, generator
        )
        trajectory = [] if return_intermediates else None
        step_indices = (
            self.scheduler.refinement_indices()
            if canonical is ConditionType.refinement
            else list(range(len(self.scheduler.timesteps) - 1, -1, -1))
        )
        for index in step_indices:
            step = self.scheduler.ddim_timesteps[index]
            timestep = torch.full(
                (batch_size,), int(step.item()), device=self.device, dtype=torch.long
            )
            if real_layout is not None and fix_mask is not None:
                sample[fix_mask] = real_layout[fix_mask]
            model_output = self.model(sample=sample, timestep=timestep).sample
            out = self.scheduler.step(
                model_output, timestep, sample, index=index, generator=generator
            )
            sample = out.prev_sample
            if real_layout is not None and fix_mask is not None:
                sample[fix_mask] = real_layout[fix_mask]
            if trajectory is not None:
                trajectory.append(sample.detach().cpu())
        decoded = self.processor.decode(sample.detach().cpu())
        bbox_out = decoded.bbox
        mask_out = decoded.mask
        if beautify:
            overlap = beautify_overlap_weight
            if overlap is None:
                overlap = (
                    1.0
                    if normalize_dataset(self.processor.dataset)
                    is DatasetName.publaynet
                    else 0.0
                )
            bbox_out, mask_out = beautify_layout(
                bbox_out,
                mask_out,
                overlap_weight=overlap,
                alignment_weight=beautify_alignment_weight,
            )
        output = LayoutGenerationOutput(
            bbox=bbox_out,
            labels=decoded.labels,
            mask=mask_out,
            id2label=decoded.id2label,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        out_type = normalize_output_type(output_type)
        if out_type is PipelineOutputType.dict:
            return dict(output)
        if out_type is PipelineOutputType.dataclass:
            return output
        assert_never(out_type)

    generate = __call__

    def save_pretrained(self, save_directory: str | Path) -> None:
        """Save pipeline components.

        Args:
            save_directory: Output directory.
        """
        super().save_pretrained(save_directory)
        self.processor.save_pretrained(save_directory)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        processor: LaceProcessor | None = None,
    ) -> "LacePipeline":
        """Load a saved LACE pipeline.

        Args:
            pretrained_model_name_or_path: Local path or Hub id.
            processor: Optional processor override.

        Returns:
            Loaded pipeline with the serialized processor attached.
        """
        loaded_processor = (
            LaceProcessor.from_pretrained(pretrained_model_name_or_path)
            if processor is None
            else processor
        )
        pipe = super().from_pretrained(pretrained_model_name_or_path)
        pipe.processor = loaded_processor
        return pipe

    def _build_fix_mask(
        self,
        condition_type: ConditionType,
        real_layout: Float[torch.Tensor, "batch elements channels"] | None,
        completion_ratio: float,
        generator: torch.Generator | None,
    ) -> Bool[torch.Tensor, "batch elements channels"] | None:
        """Build the fixed-channel mask for conditional generation."""
        if real_layout is None or condition_type is ConditionType.refinement:
            return None

        batch_size, seq_len, seq_dim = real_layout.shape
        num_class = seq_dim - 4

        if condition_type is ConditionType.label:
            fix_mask = torch.zeros_like(real_layout, dtype=torch.bool)
            fix_mask[:, :, :num_class] = True
            return fix_mask

        if condition_type is ConditionType.label_size:
            fix_mask = torch.zeros_like(real_layout, dtype=torch.bool)
            fix_indices = list(range(num_class)) + [num_class + 2, num_class + 3]
            fix_mask[:, :, fix_indices] = True
            return fix_mask

        if condition_type is ConditionType.completion:
            labels = real_layout[:, :, :num_class].argmax(dim=2)
            real_mask = labels != (num_class - 1)
            cutoff = torch.rand(
                (), device=real_layout.device, generator=generator
            ).item()
            element_mask = (
                torch.rand(
                    batch_size,
                    seq_len,
                    device=real_layout.device,
                    generator=generator,
                )
                <= cutoff * completion_ratio
            ) & real_mask
            return element_mask.unsqueeze(-1).expand(-1, -1, seq_dim)

        if condition_type is ConditionType.unconditional:
            return None
        raise ValueError(f"Unsupported LACE condition_type: {condition_type}")

components property

components: dict[
    str,
    LaceTransformerModel | LaceScheduler | LaceProcessor,
]

Expose modules and processor metadata for Diffusers serialization.

__init__

__init__(
    model: LaceTransformerModel,
    scheduler: LaceScheduler,
    processor: LaceProcessor,
) -> None

Attach the converted LACE denoiser, scheduler, and processor.

Source code in models/lace/src/lace/pipeline_lace.py
143
144
145
146
147
148
149
150
151
152
153
154
155
def __init__(
    self,
    model: LaceTransformerModel,
    scheduler: LaceScheduler,
    processor: LaceProcessor,
) -> None:
    """Attach the converted LACE denoiser, scheduler, and processor."""
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler)
    self.model = model
    self.scheduler = scheduler
    self.processor = processor
    self.model.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    num_inference_steps: int | None = None,
    generator: Generator | None = None,
    seed: int | None = None,
    condition_type: ConditionType
    | str
    | None = ConditionType.unconditional,
    bbox: Float[Tensor, "batch elements 4"] | None = None,
    labels: Int[Tensor, "batch elements"] | None = None,
    mask: Bool[Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    completion_ratio: float = 0.2,
    refinement_noise: float = 0.1,
    beautify: bool = False,
    beautify_overlap_weight: float | None = None,
    beautify_alignment_weight: float = 1.0,
    output_type: PipelineOutputType
    | str = PipelineOutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Run LACE denoising and return generated layouts.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate for unconditional calls.

1
num_inference_steps int | None

Number of DDIM steps. Uses scheduler default if omitted.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
seed int | None

Convenience seed used only when generator is absent.

None
condition_type ConditionType | str | None

Conditioning mode or alias.

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

Conditioning boxes for non-unconditional modes.

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

Conditioning labels for non-unconditional modes.

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

Optional conditioning mask.

None
box_format BoxFormat | str

Input box format for conditioning boxes.

xywh
normalized bool

Whether conditioning boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized is false.

None
completion_ratio float

Maximum random completion fraction.

0.2
refinement_noise float

Noise scale for refinement conditioning.

0.1
beautify bool

Whether to run the aesthetic post-optimization.

False
beautify_overlap_weight float | None

Optional overlap penalty override.

None
beautify_alignment_weight float

Alignment penalty weight.

1.0
output_type PipelineOutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to return the denoising trajectory.

False

Returns:

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

Layout output dataclass or a dictionary.

Raises:

Type Description
ValueError

If a condition/output mode is unsupported or required conditioning tensors are missing.

Source code in models/lace/src/lace/pipeline_lace.py
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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    num_inference_steps: int | None = None,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    condition_type: ConditionType | str | None = ConditionType.unconditional,
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
    labels: Int[torch.Tensor, "batch elements"] | None = None,
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    completion_ratio: float = 0.2,
    refinement_noise: float = 0.1,
    beautify: bool = False,
    beautify_overlap_weight: float | None = None,
    beautify_alignment_weight: float = 1.0,
    output_type: PipelineOutputType | str = PipelineOutputType.dataclass,
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Run LACE denoising and return generated layouts.

    Args:
        batch_size: Number of layouts to generate for unconditional calls.
        num_inference_steps: Number of DDIM steps. Uses scheduler default if
            omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        seed: Convenience seed used only when ``generator`` is absent.
        condition_type: Conditioning mode or alias.
        bbox: Conditioning boxes for non-unconditional modes.
        labels: Conditioning labels for non-unconditional modes.
        mask: Optional conditioning mask.
        box_format: Input box format for conditioning boxes.
        normalized: Whether conditioning boxes are normalized.
        canvas_size: Pixel canvas size required when ``normalized`` is false.
        completion_ratio: Maximum random completion fraction.
        refinement_noise: Noise scale for refinement conditioning.
        beautify: Whether to run the aesthetic post-optimization.
        beautify_overlap_weight: Optional overlap penalty override.
        beautify_alignment_weight: Alignment penalty weight.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to return the denoising trajectory.

    Returns:
        Layout output dataclass or a dictionary.

    Raises:
        ValueError: If a condition/output mode is unsupported or required
            conditioning tensors are missing.
    """
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    encoded = None
    if canonical is not ConditionType.unconditional:
        if bbox is None or labels is None:
            raise ValueError(
                f"bbox and labels are required for condition_type={condition_type}"
            )

        encoded = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        batch_size = encoded[LACE_LAYOUT_KEY].shape[0]
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.processor.max_seq_length,
        self.processor.seq_dim,
        device=self.device,
        generator=generator,
    )
    if canonical is ConditionType.refinement:
        assert encoded is not None
        noise = torch.randn(
            encoded[LACE_BBOX_KEY].shape,
            dtype=encoded[LACE_BBOX_KEY].dtype,
            device=encoded[LACE_BBOX_KEY].device,
            generator=generator,
        )
        noisy_bbox = (encoded[LACE_BBOX_KEY] + refinement_noise * noise).clamp(0, 1)
        sample = self.processor.encode(
            noisy_bbox.to(self.device),
            encoded[LACE_LABELS_KEY].to(self.device),
            encoded[LACE_MASK_KEY].to(self.device),
        )
    real_layout = (
        None if encoded is None else encoded[LACE_LAYOUT_KEY].to(self.device)
    )
    fix_mask = self._build_fix_mask(
        canonical, real_layout, completion_ratio, generator
    )
    trajectory = [] if return_intermediates else None
    step_indices = (
        self.scheduler.refinement_indices()
        if canonical is ConditionType.refinement
        else list(range(len(self.scheduler.timesteps) - 1, -1, -1))
    )
    for index in step_indices:
        step = self.scheduler.ddim_timesteps[index]
        timestep = torch.full(
            (batch_size,), int(step.item()), device=self.device, dtype=torch.long
        )
        if real_layout is not None and fix_mask is not None:
            sample[fix_mask] = real_layout[fix_mask]
        model_output = self.model(sample=sample, timestep=timestep).sample
        out = self.scheduler.step(
            model_output, timestep, sample, index=index, generator=generator
        )
        sample = out.prev_sample
        if real_layout is not None and fix_mask is not None:
            sample[fix_mask] = real_layout[fix_mask]
        if trajectory is not None:
            trajectory.append(sample.detach().cpu())
    decoded = self.processor.decode(sample.detach().cpu())
    bbox_out = decoded.bbox
    mask_out = decoded.mask
    if beautify:
        overlap = beautify_overlap_weight
        if overlap is None:
            overlap = (
                1.0
                if normalize_dataset(self.processor.dataset)
                is DatasetName.publaynet
                else 0.0
            )
        bbox_out, mask_out = beautify_layout(
            bbox_out,
            mask_out,
            overlap_weight=overlap,
            alignment_weight=beautify_alignment_weight,
        )
    output = LayoutGenerationOutput(
        bbox=bbox_out,
        labels=decoded.labels,
        mask=mask_out,
        id2label=decoded.id2label,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    out_type = normalize_output_type(output_type)
    if out_type is PipelineOutputType.dict:
        return dict(output)
    if out_type is PipelineOutputType.dataclass:
        return output
    assert_never(out_type)

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save pipeline components.

Parameters:

Name Type Description Default
save_directory str | Path

Output directory.

required
Source code in models/lace/src/lace/pipeline_lace.py
325
326
327
328
329
330
331
332
def save_pretrained(self, save_directory: str | Path) -> None:
    """Save pipeline components.

    Args:
        save_directory: Output directory.
    """
    super().save_pretrained(save_directory)
    self.processor.save_pretrained(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    processor: LaceProcessor | None = None,
) -> "LacePipeline"

Load a saved LACE pipeline.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Local path or Hub id.

required
processor LaceProcessor | None

Optional processor override.

None

Returns:

Type Description
'LacePipeline'

Loaded pipeline with the serialized processor attached.

Source code in models/lace/src/lace/pipeline_lace.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    processor: LaceProcessor | None = None,
) -> "LacePipeline":
    """Load a saved LACE pipeline.

    Args:
        pretrained_model_name_or_path: Local path or Hub id.
        processor: Optional processor override.

    Returns:
        Loaded pipeline with the serialized processor attached.
    """
    loaded_processor = (
        LaceProcessor.from_pretrained(pretrained_model_name_or_path)
        if processor is None
        else processor
    )
    pipe = super().from_pretrained(pretrained_model_name_or_path)
    pipe.processor = loaded_processor
    return pipe

PipelineOutputType

Bases: StrEnum

Supported LACE pipeline output containers.

Source code in models/lace/src/lace/pipeline_lace.py
32
33
34
35
36
class PipelineOutputType(StrEnum):
    """Supported LACE pipeline output containers."""

    dataclass = auto()
    dict = auto()

LaceProcessor

Bases: ProcessorMixin

Encode public layout tensors into the continuous LACE sequence format.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset name serialized with the processor config.

required
labels list[str]

Ordered category labels without the padding label.

required
max_seq_length int

Maximum number of layout elements.

25

Examples:

>>> processor = LaceProcessor.from_dataset("publaynet")
>>> processor.seq_dim
10
Source code in models/lace/src/lace/processing_lace.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
class LaceProcessor(ProcessorMixin):
    """Encode public layout tensors into the continuous LACE sequence format.

    Args:
        dataset: Canonical dataset name serialized with the processor config.
        labels: Ordered category labels without the padding label.
        max_seq_length: Maximum number of layout elements.

    Examples:
        >>> processor = LaceProcessor.from_dataset("publaynet")
        >>> processor.seq_dim
        10
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset: DatasetName | str,
        labels: list[str],
        max_seq_length: int = 25,
    ) -> None:
        """Initialize processor metadata.

        Args:
            dataset: Canonical dataset name.
            labels: Ordered category labels without padding.
            max_seq_length: Maximum number of layout elements.
        """
        super().__init__()
        self.dataset = str(normalize_dataset(dataset))
        self.labels = tuple(labels)
        self.max_seq_length = max_seq_length

    @classmethod
    def from_dataset(cls, dataset: DatasetName | str) -> "LaceProcessor":
        """Create a processor from built-in dataset metadata.

        Args:
            dataset: LACE dataset name or alias.

        Returns:
            Processor configured for the dataset.

        Raises:
            ValueError: If the dataset is unsupported.

        Examples:
            >>> LaceProcessor.from_dataset("rico13").pad_label_id
            13
        """
        spec = get_dataset_spec(dataset)
        return cls(
            dataset=str(spec.dataset),
            labels=[str(label) for label in spec.labels],
            max_seq_length=spec.max_seq_length,
        )

    @property
    def id2label(self) -> dict[int, str]:
        """Return the category id to label mapping."""
        return dict(enumerate(self.labels))

    @property
    def pad_label_id(self) -> int:
        """Return the padding label id."""
        return len(self.labels)

    @property
    def num_classes_with_pad(self) -> int:
        """Return the label-channel count including padding."""
        return len(self.labels) + 1

    @property
    def seq_dim(self) -> int:
        """Return the latent per-element feature size."""
        return self.num_classes_with_pad + 4

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

        Args:
            bbox: Boxes in ``box_format``.
            labels: Integer category labels.
            mask: Optional valid-element mask.
            box_format: Input box format.
            normalized: Whether input coordinates are already normalized.
            canvas_size: Pixel canvas size required when ``normalized`` is false.

        Returns:
            Dictionary containing encoded layout, normalized boxes, labels, and mask.

        Raises:
            ValueError: If pixel boxes are passed without ``canvas_size`` or if
                ``box_format`` is unsupported.

        Examples:
            >>> processor = LaceProcessor.from_dataset("publaynet")
            >>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
            >>> tuple(out["layout"].shape)
            (1, 25, 10)
        """
        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t)
        return {
            LACE_LAYOUT_KEY: self.encode(bbox_t, labels_t, mask_t),
            LACE_BBOX_KEY: bbox_t,
            LACE_LABELS_KEY: labels_t,
            LACE_MASK_KEY: mask_t,
        }

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        max_seq_length: int | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch padded_elements 4"],
        Int[torch.Tensor, "batch padded_elements"],
        Bool[torch.Tensor, "batch padded_elements"],
    ]:
        """Pad a batch to ``max_seq_length``.

        Args:
            bbox: Normalized boxes with shape ``(batch, seq, 4)``.
            labels: Integer labels with shape ``(batch, seq)``.
            mask: Optional valid-element mask.
            max_seq_length: Optional override for output length.

        Returns:
            Padded boxes, labels, and mask.

        Raises:
            ValueError: If the input has too many elements.
        """
        max_len = max_seq_length or self.max_seq_length
        if bbox.shape[1] > max_len:
            raise ValueError(f"LACE supports at most {max_len} elements")

        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        pad_count = max_len - bbox.shape[1]
        if pad_count:
            bbox_pad = torch.zeros(
                bbox.shape[0], pad_count, 4, dtype=bbox.dtype, device=bbox.device
            )
            label_pad = torch.full(
                (labels.shape[0], pad_count),
                self.pad_label_id,
                dtype=labels.dtype,
                device=labels.device,
            )
            mask_pad = torch.zeros(
                mask.shape[0], pad_count, dtype=torch.bool, device=mask.device
            )
            bbox = torch.cat((bbox, bbox_pad), dim=1)
            labels = torch.cat((labels, label_pad), dim=1)
            mask = torch.cat((mask, mask_pad), dim=1)
        labels = labels.clone()
        labels[~mask] = self.pad_label_id
        return bbox, labels, mask

    def encode(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> Float[torch.Tensor, "batch padded_elements channels"]:
        """Encode normalized boxes and labels into the LACE latent range.

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

        Returns:
            Tensor with one-hot labels followed by box channels in ``[-1, 1]``.
        """
        bbox, labels, mask = self.pad(bbox, labels, mask)
        bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
        labels = labels.clamp(0, self.pad_label_id)
        labels[~mask] = self.pad_label_id
        one_hot = torch.nn.functional.one_hot(
            labels, num_classes=self.num_classes_with_pad
        ).to(dtype=bbox.dtype, device=bbox.device)
        return torch.cat((one_hot, bbox_in), dim=-1)

    def decode(
        self, layout: Float[torch.Tensor, "batch elements channels"], clamp: bool = True
    ) -> LayoutGenerationOutput:
        """Decode a LACE layout tensor into public output fields.

        Args:
            layout: Tensor with one-hot label channels and box channels.
            clamp: Whether to clamp latent box channels before conversion.

        Returns:
            Layout generation output with boxes in normalized center ``xywh``.
        """
        decoded = layout.clone()
        bbox_latent = decoded[:, :, self.num_classes_with_pad :]
        if clamp:
            bbox_latent = bbox_latent.clamp(-1.0, 1.0)
        bbox = bbox_latent / 2 + 0.5
        labels = decoded[:, :, : self.num_classes_with_pad].argmax(dim=2).long()
        mask = labels != self.pad_label_id
        return LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=self.id2label,
            intermediates={"dataset": self.dataset},
        )

    def save_pretrained(  # ty: ignore[invalid-method-override]
        self, save_directory: str | Path
    ) -> None:
        """Save processor config to a Diffusers directory.

        Args:
            save_directory: Directory where ``processor_config.json`` is written.
        """
        super().save_pretrained(save_directory)

    @classmethod
    def from_pretrained(  # ty: ignore[invalid-method-override]
        cls,
        pretrained_model_name_or_path: str | Path,
        cache_dir: str | Path | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
    ) -> "LaceProcessor":
        """Load processor config from a Diffusers directory.

        Args:
            pretrained_model_name_or_path: Directory or Hub id containing
                ``processor_config.json``.
            cache_dir: Optional Hugging Face cache directory.
            force_download: Whether to force a fresh download.
            local_files_only: Whether to avoid network access.
            token: Optional Hugging Face token.
            revision: Hub revision to load.

        Returns:
            Loaded processor.
        """
        return super().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,
        )

id2label property

id2label: dict[int, str]

Return the category id to label mapping.

pad_label_id property

pad_label_id: int

Return the padding label id.

num_classes_with_pad property

num_classes_with_pad: int

Return the label-channel count including padding.

seq_dim property

seq_dim: int

Return the latent per-element feature size.

__init__

__init__(
    dataset: DatasetName | str,
    labels: list[str],
    max_seq_length: int = 25,
) -> None

Initialize processor metadata.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset name.

required
labels list[str]

Ordered category labels without padding.

required
max_seq_length int

Maximum number of layout elements.

25
Source code in models/lace/src/lace/processing_lace.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(
    self,
    dataset: DatasetName | str,
    labels: list[str],
    max_seq_length: int = 25,
) -> None:
    """Initialize processor metadata.

    Args:
        dataset: Canonical dataset name.
        labels: Ordered category labels without padding.
        max_seq_length: Maximum number of layout elements.
    """
    super().__init__()
    self.dataset = str(normalize_dataset(dataset))
    self.labels = tuple(labels)
    self.max_seq_length = max_seq_length

from_dataset classmethod

from_dataset(dataset: DatasetName | str) -> 'LaceProcessor'

Create a processor from built-in dataset metadata.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required

Returns:

Type Description
'LaceProcessor'

Processor configured for the dataset.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> LaceProcessor.from_dataset("rico13").pad_label_id
13
Source code in models/lace/src/lace/processing_lace.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@classmethod
def from_dataset(cls, dataset: DatasetName | str) -> "LaceProcessor":
    """Create a processor from built-in dataset metadata.

    Args:
        dataset: LACE dataset name or alias.

    Returns:
        Processor configured for the dataset.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> LaceProcessor.from_dataset("rico13").pad_label_id
        13
    """
    spec = get_dataset_spec(dataset)
    return cls(
        dataset=str(spec.dataset),
        labels=[str(label) for label in spec.labels],
        max_seq_length=spec.max_seq_length,
    )

__call__

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

Encode a public layout batch.

Parameters:

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

Boxes in box_format.

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

Integer category labels.

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

Optional valid-element mask.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input coordinates are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized is false.

None

Returns:

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

Dictionary containing encoded layout, normalized boxes, labels, and mask.

Raises:

Type Description
ValueError

If pixel boxes are passed without canvas_size or if box_format is unsupported.

Examples:

>>> processor = LaceProcessor.from_dataset("publaynet")
>>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
>>> tuple(out["layout"].shape)
(1, 25, 10)
Source code in models/lace/src/lace/processing_lace.py
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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode a public layout batch.

    Args:
        bbox: Boxes in ``box_format``.
        labels: Integer category labels.
        mask: Optional valid-element mask.
        box_format: Input box format.
        normalized: Whether input coordinates are already normalized.
        canvas_size: Pixel canvas size required when ``normalized`` is false.

    Returns:
        Dictionary containing encoded layout, normalized boxes, labels, and mask.

    Raises:
        ValueError: If pixel boxes are passed without ``canvas_size`` or if
            ``box_format`` is unsupported.

    Examples:
        >>> processor = LaceProcessor.from_dataset("publaynet")
        >>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
        >>> tuple(out["layout"].shape)
        (1, 25, 10)
    """
    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t)
    return {
        LACE_LAYOUT_KEY: self.encode(bbox_t, labels_t, mask_t),
        LACE_BBOX_KEY: bbox_t,
        LACE_LABELS_KEY: labels_t,
        LACE_MASK_KEY: mask_t,
    }

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
    max_seq_length: int | None = None,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]

Pad a batch to max_seq_length.

Parameters:

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

Normalized boxes with shape (batch, seq, 4).

required
labels Int[Tensor, 'batch elements']

Integer labels with shape (batch, seq).

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

Optional valid-element mask.

None
max_seq_length int | None

Optional override for output length.

None

Returns:

Type Description
tuple[Float[Tensor, 'batch padded_elements 4'], Int[Tensor, 'batch padded_elements'], Bool[Tensor, 'batch padded_elements']]

Padded boxes, labels, and mask.

Raises:

Type Description
ValueError

If the input has too many elements.

Source code in models/lace/src/lace/processing_lace.py
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
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    max_seq_length: int | None = None,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]:
    """Pad a batch to ``max_seq_length``.

    Args:
        bbox: Normalized boxes with shape ``(batch, seq, 4)``.
        labels: Integer labels with shape ``(batch, seq)``.
        mask: Optional valid-element mask.
        max_seq_length: Optional override for output length.

    Returns:
        Padded boxes, labels, and mask.

    Raises:
        ValueError: If the input has too many elements.
    """
    max_len = max_seq_length or self.max_seq_length
    if bbox.shape[1] > max_len:
        raise ValueError(f"LACE supports at most {max_len} elements")

    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    pad_count = max_len - bbox.shape[1]
    if pad_count:
        bbox_pad = torch.zeros(
            bbox.shape[0], pad_count, 4, dtype=bbox.dtype, device=bbox.device
        )
        label_pad = torch.full(
            (labels.shape[0], pad_count),
            self.pad_label_id,
            dtype=labels.dtype,
            device=labels.device,
        )
        mask_pad = torch.zeros(
            mask.shape[0], pad_count, dtype=torch.bool, device=mask.device
        )
        bbox = torch.cat((bbox, bbox_pad), dim=1)
        labels = torch.cat((labels, label_pad), dim=1)
        mask = torch.cat((mask, mask_pad), dim=1)
    labels = labels.clone()
    labels[~mask] = self.pad_label_id
    return bbox, labels, mask

encode

encode(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch padded_elements channels"]

Encode normalized boxes and labels into the LACE latent range.

Parameters:

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

Normalized center xywh boxes.

required
labels Int[Tensor, 'batch elements']

Integer labels.

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

Optional valid-element mask.

None

Returns:

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

Tensor with one-hot labels followed by box channels in [-1, 1].

Source code in models/lace/src/lace/processing_lace.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def encode(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch padded_elements channels"]:
    """Encode normalized boxes and labels into the LACE latent range.

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

    Returns:
        Tensor with one-hot labels followed by box channels in ``[-1, 1]``.
    """
    bbox, labels, mask = self.pad(bbox, labels, mask)
    bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
    labels = labels.clamp(0, self.pad_label_id)
    labels[~mask] = self.pad_label_id
    one_hot = torch.nn.functional.one_hot(
        labels, num_classes=self.num_classes_with_pad
    ).to(dtype=bbox.dtype, device=bbox.device)
    return torch.cat((one_hot, bbox_in), dim=-1)

decode

decode(
    layout: Float[Tensor, "batch elements channels"],
    clamp: bool = True,
) -> LayoutGenerationOutput

Decode a LACE layout tensor into public output fields.

Parameters:

Name Type Description Default
layout Float[Tensor, 'batch elements channels']

Tensor with one-hot label channels and box channels.

required
clamp bool

Whether to clamp latent box channels before conversion.

True

Returns:

Type Description
LayoutGenerationOutput

Layout generation output with boxes in normalized center xywh.

Source code in models/lace/src/lace/processing_lace.py
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
def decode(
    self, layout: Float[torch.Tensor, "batch elements channels"], clamp: bool = True
) -> LayoutGenerationOutput:
    """Decode a LACE layout tensor into public output fields.

    Args:
        layout: Tensor with one-hot label channels and box channels.
        clamp: Whether to clamp latent box channels before conversion.

    Returns:
        Layout generation output with boxes in normalized center ``xywh``.
    """
    decoded = layout.clone()
    bbox_latent = decoded[:, :, self.num_classes_with_pad :]
    if clamp:
        bbox_latent = bbox_latent.clamp(-1.0, 1.0)
    bbox = bbox_latent / 2 + 0.5
    labels = decoded[:, :, : self.num_classes_with_pad].argmax(dim=2).long()
    mask = labels != self.pad_label_id
    return LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=self.id2label,
        intermediates={"dataset": self.dataset},
    )

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save processor config to a Diffusers directory.

Parameters:

Name Type Description Default
save_directory str | Path

Directory where processor_config.json is written.

required
Source code in models/lace/src/lace/processing_lace.py
268
269
270
271
272
273
274
275
276
def save_pretrained(  # ty: ignore[invalid-method-override]
    self, save_directory: str | Path
) -> None:
    """Save processor config to a Diffusers directory.

    Args:
        save_directory: Directory where ``processor_config.json`` is written.
    """
    super().save_pretrained(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    cache_dir: str | Path | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
) -> "LaceProcessor"

Load processor config from a Diffusers directory.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Directory or Hub id containing processor_config.json.

required
cache_dir str | Path | None

Optional Hugging Face cache directory.

None
force_download bool

Whether to force a fresh download.

False
local_files_only bool

Whether to avoid network access.

False
token str | bool | None

Optional Hugging Face token.

None
revision str

Hub revision to load.

'main'

Returns:

Type Description
'LaceProcessor'

Loaded processor.

Source code in models/lace/src/lace/processing_lace.py
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
@classmethod
def from_pretrained(  # ty: ignore[invalid-method-override]
    cls,
    pretrained_model_name_or_path: str | Path,
    cache_dir: str | Path | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
) -> "LaceProcessor":
    """Load processor config from a Diffusers directory.

    Args:
        pretrained_model_name_or_path: Directory or Hub id containing
            ``processor_config.json``.
        cache_dir: Optional Hugging Face cache directory.
        force_download: Whether to force a fresh download.
        local_files_only: Whether to avoid network access.
        token: Optional Hugging Face token.
        revision: Hub revision to load.

    Returns:
        Loaded processor.
    """
    return super().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,
    )

BetaSchedule

Bases: StrEnum

Supported DDPM beta schedules.

Origin

These schedule names mirror CompVis latent-diffusion make_beta_schedule aliases used by the LACE scheduler.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class BetaSchedule(StrEnum):
    """Supported DDPM beta schedules.

    Origin:
        These schedule names mirror CompVis latent-diffusion
        ``make_beta_schedule`` aliases used by the LACE scheduler.
    """

    linear = auto()
    const = auto()
    quad = auto()
    jsd = auto()
    sigmoid = auto()
    cosine = auto()
    cosine_reverse = auto()
    cosine_anneal = auto()

DDIMDiscretization

Bases: StrEnum

Supported DDIM timestep discretization methods.

Origin

These discretization names mirror CompVis latent-diffusion make_ddim_timesteps modes used by the LACE scheduler.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
50
51
52
53
54
55
56
57
58
59
60
class DDIMDiscretization(StrEnum):
    """Supported DDIM timestep discretization methods.

    Origin:
        These discretization names mirror CompVis latent-diffusion
        ``make_ddim_timesteps`` modes used by the LACE scheduler.
    """

    uniform = auto()
    quad = auto()
    new = auto()

LaceScheduler

Bases: SchedulerMixin, ConfigMixin

Scheduler for the converted LACE diffusion process.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of timesteps used during training.

1000
beta_schedule BetaSchedule | str

Beta schedule enum or string value.

cosine
ddim_num_steps int

Default number of inference steps.

100
ddim_discretize DDIMDiscretization | str

DDIM discretization enum or string value.

uniform
eta float

Stochasticity parameter used by DDIM.

0.0
Source code in models/lace/src/lace/scheduling_lace.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class LaceScheduler(SchedulerMixin, ConfigMixin):
    """Scheduler for the converted LACE diffusion process.

    Args:
        num_train_timesteps: Number of timesteps used during training.
        beta_schedule: Beta schedule enum or string value.
        ddim_num_steps: Default number of inference steps.
        ddim_discretize: DDIM discretization enum or string value.
        eta: Stochasticity parameter used by DDIM.
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 1000,
        beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
        ddim_num_steps: int = 100,
        ddim_discretize: DDIMDiscretization | str = DDIMDiscretization.uniform,
        eta: float = 0.0,
    ) -> None:
        """Initialize scheduler state and default timesteps."""
        self.num_train_timesteps = num_train_timesteps
        canonical_beta = normalize_beta_schedule(beta_schedule)
        canonical_ddim = normalize_ddim_discretization(ddim_discretize)
        self.beta_schedule = str(canonical_beta)
        self.ddim_num_steps = ddim_num_steps
        self.ddim_discretize = str(canonical_ddim)
        self.eta = eta
        betas = make_beta_schedule(
            canonical_beta,
            num_timesteps=num_train_timesteps,
            start=0.0001,
            end=0.02,
        ).float()
        alphas = 1.0 - betas
        self.alphas_cumprod = alphas.cumprod(dim=0)
        self.timesteps = torch.empty(0, dtype=torch.long)
        self.set_timesteps(ddim_num_steps)

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set the inference timesteps.

        Args:
            num_inference_steps: Number of denoising steps. Uses the configured
                default when omitted.
            device: Optional device for scheduler tensors.
        """
        steps = num_inference_steps or self.ddim_num_steps
        ddim = make_ddim_timesteps(
            self.ddim_discretize, steps, self.num_train_timesteps
        )
        self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
        self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
        alphas_cumprod = self.alphas_cumprod.to(device)
        self.ddim_alphas = alphas_cumprod[self.ddim_timesteps]
        self.ddim_alphas_prev = torch.as_tensor(
            [alphas_cumprod[0].item()]
            + alphas_cumprod[self.ddim_timesteps[:-1]].tolist(),
            dtype=torch.float32,
            device=device,
        )
        self.ddim_sigmas = self.eta * torch.sqrt(
            (1 - self.ddim_alphas_prev)
            / (1 - self.ddim_alphas)
            * (1 - self.ddim_alphas / self.ddim_alphas_prev)
        )
        self.sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

    def add_noise(
        self,
        original_samples: Float[torch.Tensor, "batch elements channels"],
        noise: Float[torch.Tensor, "batch elements channels"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Add forward-process noise to clean samples.

        Args:
            original_samples: Clean layout tensor.
            noise: Noise tensor with the same shape.
            timesteps: Per-sample timestep ids.

        Returns:
            Noisy samples at the requested timesteps.
        """
        alphas = self.alphas_cumprod.to(original_samples.device)
        sqrt_alpha = torch.gather(alphas.sqrt(), 0, timesteps).reshape(-1, 1, 1)
        sqrt_one_minus = torch.gather(torch.sqrt(1.0 - alphas), 0, timesteps).reshape(
            -1, 1, 1
        )
        return sqrt_alpha * original_samples + sqrt_one_minus * noise

    def initial_sample(
        self,
        batch_size: int,
        seq_len: int,
        seq_dim: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
        stochastic: bool = True,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Create the initial denoising sample.

        Args:
            batch_size: Number of layouts.
            seq_len: Number of elements per layout.
            seq_dim: Number of channels per element.
            device: Device for the output tensor.
            generator: Optional torch generator.
            stochastic: Whether to sample noise or return zeros.

        Returns:
            Initial sample tensor.
        """
        if not stochastic:
            return torch.zeros(batch_size, seq_len, seq_dim, device=device)
        return torch.randn(
            batch_size, seq_len, seq_dim, device=device, generator=generator
        )

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch elements channels"],
        index: int,
        generator: torch.Generator | None = None,
    ) -> LaceSchedulerOutput:
        """Take one reverse diffusion step.

        Args:
            model_output: Predicted noise from the denoiser.
            timestep: Public timestep tensor, kept for scheduler compatibility.
            sample: Current sample.
            index: Index into the scheduler timestep buffers.
            generator: Optional torch generator for stochastic DDIM noise.

        Returns:
            Previous sample and predicted clean sample.
        """
        del timestep
        alpha_t = self.ddim_alphas[index].to(sample.device)
        alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
        sigma_t = self.ddim_sigmas[index].to(sample.device)
        sqrt_one_minus = self.sqrt_one_minus_alphas[index].to(sample.device)
        pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
        direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
        noise = sigma_t * torch.randn(
            sample.shape,
            dtype=sample.dtype,
            device=sample.device,
            generator=generator,
        )
        prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
        return LaceSchedulerOutput(
            prev_sample=prev_sample, pred_original_sample=pred_original
        )

    def refinement_indices(self, max_timestep: int = 201) -> list[int]:
        """Return scheduler indices used by LACE refinement sampling.

        Args:
            max_timestep: Maximum one-indexed DDIM timestep included.

        Returns:
            Descending list of scheduler buffer indices.
        """
        total = int(torch.sum(self.ddim_timesteps <= max_timestep).item())
        return list(range(total - 1, -1, -1))

__init__

__init__(
    *,
    num_train_timesteps: int = 1000,
    beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
    ddim_num_steps: int = 100,
    ddim_discretize: DDIMDiscretization
    | str = DDIMDiscretization.uniform,
    eta: float = 0.0,
) -> None

Initialize scheduler state and default timesteps.

Source code in models/lace/src/lace/scheduling_lace.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 1000,
    beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
    ddim_num_steps: int = 100,
    ddim_discretize: DDIMDiscretization | str = DDIMDiscretization.uniform,
    eta: float = 0.0,
) -> None:
    """Initialize scheduler state and default timesteps."""
    self.num_train_timesteps = num_train_timesteps
    canonical_beta = normalize_beta_schedule(beta_schedule)
    canonical_ddim = normalize_ddim_discretization(ddim_discretize)
    self.beta_schedule = str(canonical_beta)
    self.ddim_num_steps = ddim_num_steps
    self.ddim_discretize = str(canonical_ddim)
    self.eta = eta
    betas = make_beta_schedule(
        canonical_beta,
        num_timesteps=num_train_timesteps,
        start=0.0001,
        end=0.02,
    ).float()
    alphas = 1.0 - betas
    self.alphas_cumprod = alphas.cumprod(dim=0)
    self.timesteps = torch.empty(0, dtype=torch.long)
    self.set_timesteps(ddim_num_steps)

set_timesteps

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

Set the inference timesteps.

Parameters:

Name Type Description Default
num_inference_steps int | None

Number of denoising steps. Uses the configured default when omitted.

None
device device | None

Optional device for scheduler tensors.

None
Source code in models/lace/src/lace/scheduling_lace.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set the inference timesteps.

    Args:
        num_inference_steps: Number of denoising steps. Uses the configured
            default when omitted.
        device: Optional device for scheduler tensors.
    """
    steps = num_inference_steps or self.ddim_num_steps
    ddim = make_ddim_timesteps(
        self.ddim_discretize, steps, self.num_train_timesteps
    )
    self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
    self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
    alphas_cumprod = self.alphas_cumprod.to(device)
    self.ddim_alphas = alphas_cumprod[self.ddim_timesteps]
    self.ddim_alphas_prev = torch.as_tensor(
        [alphas_cumprod[0].item()]
        + alphas_cumprod[self.ddim_timesteps[:-1]].tolist(),
        dtype=torch.float32,
        device=device,
    )
    self.ddim_sigmas = self.eta * torch.sqrt(
        (1 - self.ddim_alphas_prev)
        / (1 - self.ddim_alphas)
        * (1 - self.ddim_alphas / self.ddim_alphas_prev)
    )
    self.sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

add_noise

add_noise(
    original_samples: Float[
        Tensor, "batch elements channels"
    ],
    noise: Float[Tensor, "batch elements channels"],
    timesteps: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]

Add forward-process noise to clean samples.

Parameters:

Name Type Description Default
original_samples Float[Tensor, 'batch elements channels']

Clean layout tensor.

required
noise Float[Tensor, 'batch elements channels']

Noise tensor with the same shape.

required
timesteps Int[Tensor, 'batch']

Per-sample timestep ids.

required

Returns:

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

Noisy samples at the requested timesteps.

Source code in models/lace/src/lace/scheduling_lace.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def add_noise(
    self,
    original_samples: Float[torch.Tensor, "batch elements channels"],
    noise: Float[torch.Tensor, "batch elements channels"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Add forward-process noise to clean samples.

    Args:
        original_samples: Clean layout tensor.
        noise: Noise tensor with the same shape.
        timesteps: Per-sample timestep ids.

    Returns:
        Noisy samples at the requested timesteps.
    """
    alphas = self.alphas_cumprod.to(original_samples.device)
    sqrt_alpha = torch.gather(alphas.sqrt(), 0, timesteps).reshape(-1, 1, 1)
    sqrt_one_minus = torch.gather(torch.sqrt(1.0 - alphas), 0, timesteps).reshape(
        -1, 1, 1
    )
    return sqrt_alpha * original_samples + sqrt_one_minus * noise

initial_sample

initial_sample(
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: device,
    generator: Generator | None = None,
    stochastic: bool = True,
) -> Float[torch.Tensor, "batch elements channels"]

Create the initial denoising sample.

Parameters:

Name Type Description Default
batch_size int

Number of layouts.

required
seq_len int

Number of elements per layout.

required
seq_dim int

Number of channels per element.

required
device device

Device for the output tensor.

required
generator Generator | None

Optional torch generator.

None
stochastic bool

Whether to sample noise or return zeros.

True

Returns:

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

Initial sample tensor.

Source code in models/lace/src/lace/scheduling_lace.py
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 initial_sample(
    self,
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
    stochastic: bool = True,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Create the initial denoising sample.

    Args:
        batch_size: Number of layouts.
        seq_len: Number of elements per layout.
        seq_dim: Number of channels per element.
        device: Device for the output tensor.
        generator: Optional torch generator.
        stochastic: Whether to sample noise or return zeros.

    Returns:
        Initial sample tensor.
    """
    if not stochastic:
        return torch.zeros(batch_size, seq_len, seq_dim, device=device)
    return torch.randn(
        batch_size, seq_len, seq_dim, device=device, generator=generator
    )

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch elements channels"],
    index: int,
    generator: Generator | None = None,
) -> LaceSchedulerOutput

Take one reverse diffusion step.

Parameters:

Name Type Description Default
model_output Float[Tensor, 'batch elements channels']

Predicted noise from the denoiser.

required
timestep Int[Tensor, 'batch']

Public timestep tensor, kept for scheduler compatibility.

required
sample Float[Tensor, 'batch elements channels']

Current sample.

required
index int

Index into the scheduler timestep buffers.

required
generator Generator | None

Optional torch generator for stochastic DDIM noise.

None

Returns:

Type Description
LaceSchedulerOutput

Previous sample and predicted clean sample.

Source code in models/lace/src/lace/scheduling_lace.py
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
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch elements channels"],
    index: int,
    generator: torch.Generator | None = None,
) -> LaceSchedulerOutput:
    """Take one reverse diffusion step.

    Args:
        model_output: Predicted noise from the denoiser.
        timestep: Public timestep tensor, kept for scheduler compatibility.
        sample: Current sample.
        index: Index into the scheduler timestep buffers.
        generator: Optional torch generator for stochastic DDIM noise.

    Returns:
        Previous sample and predicted clean sample.
    """
    del timestep
    alpha_t = self.ddim_alphas[index].to(sample.device)
    alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
    sigma_t = self.ddim_sigmas[index].to(sample.device)
    sqrt_one_minus = self.sqrt_one_minus_alphas[index].to(sample.device)
    pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
    direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
    noise = sigma_t * torch.randn(
        sample.shape,
        dtype=sample.dtype,
        device=sample.device,
        generator=generator,
    )
    prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
    return LaceSchedulerOutput(
        prev_sample=prev_sample, pred_original_sample=pred_original
    )

refinement_indices

refinement_indices(max_timestep: int = 201) -> list[int]

Return scheduler indices used by LACE refinement sampling.

Parameters:

Name Type Description Default
max_timestep int

Maximum one-indexed DDIM timestep included.

201

Returns:

Type Description
list[int]

Descending list of scheduler buffer indices.

Source code in models/lace/src/lace/scheduling_lace.py
199
200
201
202
203
204
205
206
207
208
209
def refinement_indices(self, max_timestep: int = 201) -> list[int]:
    """Return scheduler indices used by LACE refinement sampling.

    Args:
        max_timestep: Maximum one-indexed DDIM timestep included.

    Returns:
        Descending list of scheduler buffer indices.
    """
    total = int(torch.sum(self.ddim_timesteps <= max_timestep).item())
    return list(range(total - 1, -1, -1))

LaceSchedulerOutput dataclass

Bases: BaseOutput

Output returned by one reverse diffusion step.

Attributes:

Name Type Description
prev_sample Float[Tensor, 'batch elements channels']

Sample for the next denoising iteration.

pred_original_sample Float[Tensor, 'batch elements channels']

Scheduler estimate of the clean layout tensor.

Source code in models/lace/src/lace/scheduling_lace.py
22
23
24
25
26
27
28
29
30
31
32
@dataclass
class LaceSchedulerOutput(BaseOutput):
    """Output returned by one reverse diffusion step.

    Attributes:
        prev_sample: Sample for the next denoising iteration.
        pred_original_sample: Scheduler estimate of the clean layout tensor.
    """

    prev_sample: Float[torch.Tensor, "batch elements channels"]
    pred_original_sample: Float[torch.Tensor, "batch elements channels"]

default_model_config

default_model_config(
    dataset: DatasetName | str,
) -> LaceModelConfigKwargs

Build the model config for a dataset.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
LaceModelConfigKwargs

Keyword arguments accepted by LaceTransformerModel.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> default_model_config("rico25")["seq_dim"]
30
Source code in models/lace/src/lace/configuration_lace.py
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
def default_model_config(dataset: DatasetName | str) -> LaceModelConfigKwargs:
    """Build the model config for a dataset.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Keyword arguments accepted by ``LaceTransformerModel``.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> default_model_config("rico25")["seq_dim"]
        30
    """
    spec = get_dataset_spec(dataset)
    return {
        "seq_dim": spec.seq_dim,
        "max_seq_length": spec.max_seq_length,
        "num_layers": spec.num_layers,
        "dim_transformer": spec.dim_transformer,
        "nhead": spec.nhead,
        "dim_feedforward": spec.dim_feedforward,
        "diffusion_step": 1000,
        "timestep_type": TimestepEmbeddingType.adalayernorm,
    }

get_dataset_spec

get_dataset_spec(
    dataset: DatasetName | str,
) -> LaceDatasetSpec

Return dataset metadata for a LACE checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
LaceDatasetSpec

Dataset specification.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> get_dataset_spec("publaynet").seq_dim
10
Source code in models/lace/src/lace/configuration_lace.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def get_dataset_spec(dataset: DatasetName | str) -> LaceDatasetSpec:
    """Return dataset metadata for a LACE checkpoint.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Dataset specification.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> get_dataset_spec("publaynet").seq_dim
        10
    """
    return DATASET_SPECS[normalize_dataset(dataset)]

normalize_dataset

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

Normalize a public dataset name to the shared dataset enum.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
DatasetName

Canonical shared dataset name.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> str(normalize_dataset("rico13_max25"))
'rico13'
Source code in models/lace/src/lace/configuration_lace.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def normalize_dataset(dataset: DatasetName | str) -> DatasetName:
    """Normalize a public dataset name to the shared dataset enum.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Canonical shared dataset name.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> str(normalize_dataset("rico13_max25"))
        'rico13'
    """
    if isinstance(dataset, DatasetName):
        return dataset
    key = dataset.lower().replace("-", "_")
    if key in _LACE_DATASET_ALIASES:
        return _LACE_DATASET_ALIASES[key]
    try:
        return normalize_dataset_name(dataset)
    except ValueError as exc:
        raise ValueError(f"Unsupported LACE dataset: {dataset}") from exc

beautify_layout

beautify_layout(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
    overlap_weight: float = 1.0,
    alignment_weight: float = 1.0,
    xy_only: bool = False,
    num_steps: int = 1000,
    lr: float = 0.0001,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Bool[torch.Tensor, "batch elements"],
]

Optimize generated boxes with overlap and alignment penalties.

Parameters:

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

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

required
mask Bool[Tensor, 'batch elements']

Valid-element mask with shape (batch, seq).

required
overlap_weight float

Weight for pairwise overlap penalties.

1.0
alignment_weight float

Weight for alignment penalties.

1.0
xy_only bool

Whether to keep width and height fixed.

False
num_steps int

Number of Adam optimization steps.

1000
lr float

Adam learning rate.

0.0001

Returns:

Type Description
tuple[Float[Tensor, 'batch elements 4'], Bool[Tensor, 'batch elements']]

Optimized boxes and updated mask.

Source code in models/lace/src/lace/constraints.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def beautify_layout(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
    overlap_weight: float = 1.0,
    alignment_weight: float = 1.0,
    xy_only: bool = False,
    num_steps: int = 1000,
    lr: float = 1e-4,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Bool[torch.Tensor, "batch elements"],
]:
    """Optimize generated boxes with overlap and alignment penalties.

    Args:
        bbox: Normalized center ``xywh`` boxes with shape ``(batch, seq, 4)``.
        mask: Valid-element mask with shape ``(batch, seq)``.
        overlap_weight: Weight for pairwise overlap penalties.
        alignment_weight: Weight for alignment penalties.
        xy_only: Whether to keep width and height fixed.
        num_steps: Number of Adam optimization steps.
        lr: Adam learning rate.

    Returns:
        Optimized boxes and updated mask.
    """
    if torch.sum(mask) == 1:
        return bbox, mask

    bbox_in = bbox
    if xy_only:
        wh = torch.abs(bbox[:, :, 2:].clone().detach())
        bbox_in = torch.cat([bbox[:, :, :2], wh], dim=2)

    bbox_in = bbox_in.clone()
    bbox_in[:, :, [0, 2]] *= 10 / 4
    bbox_in[:, :, [1, 3]] *= 10 / 6
    bbox_initial = bbox_in.clone().detach()
    bbox_param = nn.Parameter(bbox_in)
    optimizer = torch.optim.Adam([bbox_param], lr=lr)
    mse_loss = nn.MSELoss()
    mask_out = mask.clone()

    with torch.enable_grad():
        for _ in range(num_steps):
            bbox_relu = torch.relu(bbox_param)
            align_score = _layout_alignment_matrix(bbox_relu, mask_out)
            align_mask = (align_score < 1 / 64).clone().detach()
            align_loss = torch.mean(align_score * align_mask)
            piou = torch.mean(_pairwise_iou_xywh(bbox_relu, mask_out))
            mse = mse_loss(bbox_relu, bbox_initial)
            loss = mse + alignment_weight * align_loss + overlap_weight * piou
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_([bbox_param], 1.0)
            optimizer.step()
            min_wh = torch.min(bbox_relu[:, :, [2, 3]], dim=2).values
            mask_out = mask_out * (min_wh > 0.01)

    bbox_out = torch.relu(bbox_param.detach())
    bbox_out[:, :, [0, 2]] *= 4 / 10
    bbox_out[:, :, [1, 3]] *= 6 / 10
    return bbox_out, mask_out

build_pipeline_from_vendor_checkpoint

build_pipeline_from_vendor_checkpoint(
    dataset: DatasetName | str,
    checkpoint_path: str | Path,
    ddim_num_steps: int = 100,
) -> LacePipeline

Build a LACE pipeline from a vendor checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required
checkpoint_path str | Path

Path to the vendor checkpoint.

required
ddim_num_steps int

Number of DDIM inference steps configured on the scheduler.

100

Returns:

Type Description
LacePipeline

Pipeline containing converted model, scheduler, and processor.

Raises:

Type Description
TypeError

If the checkpoint payload is not a state dictionary.

ValueError

If converted keys do not match the model architecture.

Source code in models/lace/src/lace/conversion.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def build_pipeline_from_vendor_checkpoint(
    dataset: DatasetName | str,
    checkpoint_path: str | Path,
    ddim_num_steps: int = 100,
) -> LacePipeline:
    """Build a LACE pipeline from a vendor checkpoint.

    Args:
        dataset: LACE dataset name or alias.
        checkpoint_path: Path to the vendor checkpoint.
        ddim_num_steps: Number of DDIM inference steps configured on the scheduler.

    Returns:
        Pipeline containing converted model, scheduler, and processor.

    Raises:
        TypeError: If the checkpoint payload is not a state dictionary.
        ValueError: If converted keys do not match the model architecture.
    """
    model = LaceTransformerModel(**default_model_config(dataset))
    converted = convert_state_dict(load_vendor_state_dict(checkpoint_path))
    expected = set(model.state_dict())
    actual = set(converted)
    missing = sorted(expected - actual)
    unexpected = sorted(actual - expected)
    if missing or unexpected:
        raise ValueError(
            f"State dict mismatch: missing={missing[:10]}, unexpected={unexpected[:10]}"
        )

    model.load_state_dict(converted, strict=True)
    processor = LaceProcessor.from_dataset(dataset)
    scheduler = LaceScheduler(ddim_num_steps=ddim_num_steps)
    return LacePipeline(model=model, scheduler=scheduler, processor=processor)

convert_state_dict

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

Convert vendor parameter names to the Diffusers module names.

Parameters:

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

Vendor state dictionary.

required

Returns:

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

Converted state dictionary with distributed prefixes and vendor

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

positional buffers removed.

Source code in models/lace/src/lace/conversion.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def convert_state_dict(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert vendor parameter names to the Diffusers module names.

    Args:
        state_dict: Vendor state dictionary.

    Returns:
        Converted state dictionary with distributed prefixes and vendor
        positional buffers removed.
    """
    return {
        key.removeprefix("module."): value
        for key, value in state_dict.items()
        if key != "pos_embed" and key.removeprefix("module.") != "pos_embed"
    }

load_vendor_state_dict

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

Load a PyTorch checkpoint as a state dictionary.

Parameters:

Name Type Description Default
path str | Path

Path to a vendor checkpoint or state-dict file.

required

Returns:

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

Mapping from parameter names to tensors.

Raises:

Type Description
TypeError

If the checkpoint does not contain a state-dict-like object.

Source code in models/lace/src/lace/conversion.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def load_vendor_state_dict(path: str | Path) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load a PyTorch checkpoint as a state dictionary.

    Args:
        path: Path to a vendor checkpoint or state-dict file.

    Returns:
        Mapping from parameter names to tensors.

    Raises:
        TypeError: If the checkpoint does not contain a state-dict-like object.
    """
    loaded = torch.load(path, map_location="cpu")
    if isinstance(loaded, dict) and "state_dict" in loaded:
        loaded = loaded["state_dict"]
    if not isinstance(loaded, dict):
        raise TypeError(f"Expected a state_dict-like checkpoint at {path}")

    return dict(loaded)

normalize_activation

normalize_activation(
    name: ActivationName | str | ActivationFn,
) -> ActivationName | ActivationFn

Normalize an activation name while preserving custom callables.

Origin

The closed string set keeps the activation names used by VQ-Diffusion-derived LayoutDM, LACE, and LayoutFlow backbones.

Parameters:

Name Type Description Default
name ActivationName | str | ActivationFn

Activation enum, string value, or callable.

required

Returns:

Type Description
ActivationName | ActivationFn

Canonical activation enum or the original callable.

Raises:

Type Description
ValueError

If the activation name is unsupported.

Source code in lib/laygen/src/laygen/nn/activations.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def normalize_activation(
    name: ActivationName | str | ActivationFn,
) -> ActivationName | ActivationFn:
    """Normalize an activation name while preserving custom callables.

    Origin:
        The closed string set keeps the activation names used by
        VQ-Diffusion-derived LayoutDM, LACE, and LayoutFlow backbones.

    Args:
        name: Activation enum, string value, or callable.

    Returns:
        Canonical activation enum or the original callable.

    Raises:
        ValueError: If the activation name is unsupported.
    """
    if callable(name):
        return cast(ActivationFn, name)
    try:
        return ActivationName(name)
    except ValueError as exc:
        raise ValueError(f"Unsupported activation: {name}") from exc

normalize_timestep_embedding

normalize_timestep_embedding(
    timestep_type: TimestepEmbeddingType | str | None,
) -> TimestepEmbeddingType | None

Normalize a timestep embedding mode.

Origin

This normalizes the VQ-Diffusion-derived adaptive normalization mode names exposed by LayoutDM and LACE checkpoints.

Parameters:

Name Type Description Default
timestep_type TimestepEmbeddingType | str | None

Embedding enum, string value, or None.

required

Returns:

Type Description
TimestepEmbeddingType | None

Canonical embedding enum or None.

Raises:

Type Description
ValueError

If the embedding mode is unsupported.

Source code in lib/laygen/src/laygen/nn/embeddings.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def normalize_timestep_embedding(
    timestep_type: TimestepEmbeddingType | str | None,
) -> TimestepEmbeddingType | None:
    """Normalize a timestep embedding mode.

    Origin:
        This normalizes the VQ-Diffusion-derived adaptive normalization mode
        names exposed by LayoutDM and LACE checkpoints.

    Args:
        timestep_type: Embedding enum, string value, or ``None``.

    Returns:
        Canonical embedding enum or ``None``.

    Raises:
        ValueError: If the embedding mode is unsupported.
    """
    if timestep_type is None or isinstance(timestep_type, TimestepEmbeddingType):
        return timestep_type
    try:
        return TimestepEmbeddingType(timestep_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported timestep_type: {timestep_type}") from exc

lace_model_card

lace_model_card(
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> ModelCard

Create the Hugging Face model card for a LACE checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required
parity_metrics list[ParityMetric] | None

Optional parity metrics to embed in the evaluation section.

None

Returns:

Type Description
ModelCard

Rendered Hugging Face Hub model card.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Source code in models/lace/src/lace/model_card.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def lace_model_card(
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> ModelCard:
    """Create the Hugging Face model card for a LACE checkpoint.

    Args:
        dataset: LACE dataset name or alias.
        parity_metrics: Optional parity metrics to embed in the evaluation section.

    Returns:
        Rendered Hugging Face Hub model card.

    Raises:
        ValueError: If the dataset is unsupported.
    """
    dataset_key = normalize_dataset(dataset)
    dataset_name = str(dataset_key)
    model_id = _MODEL_IDS[dataset_key]
    metrics = parity_metrics
    if metrics is None:
        metrics = (
            [_PARITY_METRICS[dataset_key]] if dataset_key in _PARITY_METRICS else []
        )
    how_to_use = f"""
from lace import LacePipeline

pipe = LacePipeline.from_pretrained("{model_id}")
out = pipe(batch_size=1, seed=0, num_inference_steps=100)
print(out.bbox, out.labels, out.mask)
"""
    details = (
        "Diffusers-format conversion of the LACE checkpoint for "
        f"`{dataset_name}`. LACE is a continuous diffusion layout generation model "
        "from the paper 'Towards Aligned Layout Generation via Diffusion Model "
        "with Aesthetic Constraints' (https://arxiv.org/abs/2402.04754). "
        "The pipeline generates normalized center `xywh` layout boxes, "
        "category labels, and masks."
    )
    if dataset_key is DatasetName.rico13:
        details += (
            " The public checkpoint archive used for local parity "
            "verification does not include `rico13_best.pt`; Rico13 parity "
            "metrics are therefore not reported here."
        )
    return build_layout_model_card(
        model_id=model_id,
        model_name=f"LACE {dataset_name}",
        dataset_ids=[_DATASET_IDS[dataset_key]],
        license="mit",
        library_name="diffusers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "lace",
            "diffusers",
            dataset_name,
        ],
        model_details=details,
        intended_uses=(
            "Use this checkpoint for research and evaluation of document and UI "
            "layout generation workflows."
        ),
        limitations=(
            "The converted checkpoint follows the original LACE release and is "
            "intended for layout synthesis, not for image rendering or OCR. "
            "Generated layouts may require downstream filtering for task-specific "
            "aesthetic or overlap constraints."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{_DATASET_IDS[dataset_key]}` "
            "as released by the original LACE project."
        ),
        parity_metrics=metrics,
        citation_bibtex=_LACE_BIBTEX,
        original_implementation_url="https://github.com/puar-playground/LACE",
    )

write_lace_model_card

write_lace_model_card(
    output_dir: str | Path,
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> Path

Write the LACE model card into a model directory.

Parameters:

Name Type Description Default
output_dir str | Path

Directory where README.md is written.

required
dataset DatasetName | str

LACE dataset name or alias.

required
parity_metrics list[ParityMetric] | None

Optional parity metrics to embed in the evaluation section.

None

Returns:

Type Description
Path

Path to the written README.md.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Source code in models/lace/src/lace/model_card.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def write_lace_model_card(
    output_dir: str | Path,
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> Path:
    """Write the LACE model card into a model directory.

    Args:
        output_dir: Directory where ``README.md`` is written.
        dataset: LACE dataset name or alias.
        parity_metrics: Optional parity metrics to embed in the evaluation section.

    Returns:
        Path to the written ``README.md``.

    Raises:
        ValueError: If the dataset is unsupported.
    """
    path = Path(output_dir) / "README.md"
    path.write_text(
        str(lace_model_card(dataset, parity_metrics=parity_metrics)),
        encoding="utf-8",
    )
    return path

normalize_condition_type

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

Normalize public condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition enum, string alias, or None.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unsupported.

Examples:

>>> normalize_condition_type("cwh") is ConditionType.label_size
True
Source code in models/lace/src/lace/pipeline_lace.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize public condition aliases.

    Args:
        condition_type: Canonical condition enum, string alias, or ``None``.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unsupported.

    Examples:
        >>> normalize_condition_type("cwh") is ConditionType.label_size
        True
    """
    if isinstance(condition_type, ConditionType):
        canonical = condition_type
    elif condition_type is None:
        canonical = ConditionType.unconditional
    else:
        try:
            canonical = normalize_shared_condition_type(condition_type)
        except ValueError:
            key = condition_type.lower().replace("-", "_")
            try:
                canonical = _LACE_CONDITION_ALIASES[LaceConditionAlias(key)]
            except ValueError as exc:
                raise ValueError(
                    f"Unsupported LACE condition_type: {condition_type}"
                ) from exc

    if canonical not in _SUPPORTED_CONDITION_TYPES:
        raise ValueError(f"Unsupported LACE condition_type: {condition_type}")

    return canonical

normalize_output_type

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

Normalize public output type aliases.

Parameters:

Name Type Description Default
output_type PipelineOutputType | str

Output enum or string value.

required

Returns:

Type Description
PipelineOutputType

Canonical output type.

Raises:

Type Description
ValueError

If the output type is unsupported.

Source code in models/lace/src/lace/pipeline_lace.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def normalize_output_type(output_type: PipelineOutputType | str) -> PipelineOutputType:
    """Normalize public output type aliases.

    Args:
        output_type: Output enum or string value.

    Returns:
        Canonical output type.

    Raises:
        ValueError: If the output type is unsupported.
    """
    if isinstance(output_type, PipelineOutputType):
        return output_type
    try:
        return PipelineOutputType(output_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

normalize_beta_schedule

normalize_beta_schedule(
    schedule: BetaSchedule | str,
) -> BetaSchedule

Normalize a beta schedule value.

Origin

This preserves the CompVis latent-diffusion schedule aliases exposed by the LACE checkpoint configuration.

Parameters:

Name Type Description Default
schedule BetaSchedule | str

Schedule enum or string value.

required

Returns:

Type Description
BetaSchedule

Canonical beta schedule enum.

Raises:

Type Description
ValueError

If the schedule is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def normalize_beta_schedule(schedule: BetaSchedule | str) -> BetaSchedule:
    """Normalize a beta schedule value.

    Origin:
        This preserves the CompVis latent-diffusion schedule aliases exposed by
        the LACE checkpoint configuration.

    Args:
        schedule: Schedule enum or string value.

    Returns:
        Canonical beta schedule enum.

    Raises:
        ValueError: If the schedule is unsupported.
    """
    if isinstance(schedule, BetaSchedule):
        return schedule
    try:
        return BetaSchedule(schedule)
    except ValueError as exc:
        raise ValueError(f"Unsupported beta schedule: {schedule}") from exc

normalize_ddim_discretization

normalize_ddim_discretization(
    method: DDIMDiscretization | str,
) -> DDIMDiscretization

Normalize a DDIM timestep discretization method.

Origin

This preserves the CompVis latent-diffusion DDIM discretization aliases exposed by the LACE checkpoint configuration.

Parameters:

Name Type Description Default
method DDIMDiscretization | str

Method enum or string value.

required

Returns:

Type Description
DDIMDiscretization

Canonical DDIM discretization enum.

Raises:

Type Description
ValueError

If the method is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
def normalize_ddim_discretization(
    method: DDIMDiscretization | str,
) -> DDIMDiscretization:
    """Normalize a DDIM timestep discretization method.

    Origin:
        This preserves the CompVis latent-diffusion DDIM discretization aliases
        exposed by the LACE checkpoint configuration.

    Args:
        method: Method enum or string value.

    Returns:
        Canonical DDIM discretization enum.

    Raises:
        ValueError: If the method is unsupported.
    """
    if isinstance(method, DDIMDiscretization):
        return method
    try:
        return DDIMDiscretization(method)
    except ValueError as exc:
        raise ValueError(f"Unsupported ddim discretization: {method}") from exc

configuration_lace

Dataset configuration helpers for LACE checkpoints.

LaceDatasetSpec dataclass

Static dataset metadata used to configure a LACE checkpoint.

Attributes:

Name Type Description
dataset DatasetName

Canonical dataset name.

labels tuple[LaceLabel, ...]

Ordered category labels without the padding class.

max_seq_length int

Maximum number of layout elements.

dim_transformer int

Transformer hidden size used by the original model.

nhead int

Number of attention heads.

num_layers int

Number of transformer blocks.

dim_feedforward int

Feed-forward hidden size.

Source code in models/lace/src/lace/configuration_lace.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@dataclass(frozen=True)
class LaceDatasetSpec:
    """Static dataset metadata used to configure a LACE checkpoint.

    Attributes:
        dataset: Canonical dataset name.
        labels: Ordered category labels without the padding class.
        max_seq_length: Maximum number of layout elements.
        dim_transformer: Transformer hidden size used by the original model.
        nhead: Number of attention heads.
        num_layers: Number of transformer blocks.
        dim_feedforward: Feed-forward hidden size.
    """

    dataset: DatasetName
    labels: tuple[LaceLabel, ...]
    max_seq_length: int = 25
    dim_transformer: int = 512
    nhead: int = 16
    num_layers: int = 4
    dim_feedforward: int = 2048

    @property
    def pad_label_id(self) -> int:
        """Return the integer id reserved for padding."""
        return len(self.labels)

    @property
    def num_classes_with_pad(self) -> int:
        """Return the number of category channels including padding."""
        return len(self.labels) + 1

    @property
    def seq_dim(self) -> int:
        """Return the latent per-element feature size."""
        return self.num_classes_with_pad + 4

    @property
    def id2label(self) -> dict[int, str]:
        """Return the category id to label mapping."""
        return dict(enumerate(str(label) for label in self.labels))

pad_label_id property

pad_label_id: int

Return the integer id reserved for padding.

num_classes_with_pad property

num_classes_with_pad: int

Return the number of category channels including padding.

seq_dim property

seq_dim: int

Return the latent per-element feature size.

id2label property

id2label: dict[int, str]

Return the category id to label mapping.

LaceModelConfigKwargs

Bases: TypedDict

Keyword arguments accepted by LaceTransformerModel.

Source code in models/lace/src/lace/configuration_lace.py
68
69
70
71
72
73
74
75
76
77
78
class LaceModelConfigKwargs(TypedDict):
    """Keyword arguments accepted by ``LaceTransformerModel``."""

    seq_dim: int
    max_seq_length: int
    num_layers: int
    dim_transformer: int
    nhead: int
    dim_feedforward: int
    diffusion_step: int
    timestep_type: TimestepEmbeddingType

normalize_dataset

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

Normalize a public dataset name to the shared dataset enum.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
DatasetName

Canonical shared dataset name.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> str(normalize_dataset("rico13_max25"))
'rico13'
Source code in models/lace/src/lace/configuration_lace.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
def normalize_dataset(dataset: DatasetName | str) -> DatasetName:
    """Normalize a public dataset name to the shared dataset enum.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Canonical shared dataset name.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> str(normalize_dataset("rico13_max25"))
        'rico13'
    """
    if isinstance(dataset, DatasetName):
        return dataset
    key = dataset.lower().replace("-", "_")
    if key in _LACE_DATASET_ALIASES:
        return _LACE_DATASET_ALIASES[key]
    try:
        return normalize_dataset_name(dataset)
    except ValueError as exc:
        raise ValueError(f"Unsupported LACE dataset: {dataset}") from exc

get_dataset_spec

get_dataset_spec(
    dataset: DatasetName | str,
) -> LaceDatasetSpec

Return dataset metadata for a LACE checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
LaceDatasetSpec

Dataset specification.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> get_dataset_spec("publaynet").seq_dim
10
Source code in models/lace/src/lace/configuration_lace.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def get_dataset_spec(dataset: DatasetName | str) -> LaceDatasetSpec:
    """Return dataset metadata for a LACE checkpoint.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Dataset specification.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> get_dataset_spec("publaynet").seq_dim
        10
    """
    return DATASET_SPECS[normalize_dataset(dataset)]

default_model_config

default_model_config(
    dataset: DatasetName | str,
) -> LaceModelConfigKwargs

Build the model config for a dataset.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset enum or a supported string alias.

required

Returns:

Type Description
LaceModelConfigKwargs

Keyword arguments accepted by LaceTransformerModel.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> default_model_config("rico25")["seq_dim"]
30
Source code in models/lace/src/lace/configuration_lace.py
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
def default_model_config(dataset: DatasetName | str) -> LaceModelConfigKwargs:
    """Build the model config for a dataset.

    Args:
        dataset: Canonical dataset enum or a supported string alias.

    Returns:
        Keyword arguments accepted by ``LaceTransformerModel``.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> default_model_config("rico25")["seq_dim"]
        30
    """
    spec = get_dataset_spec(dataset)
    return {
        "seq_dim": spec.seq_dim,
        "max_seq_length": spec.max_seq_length,
        "num_layers": spec.num_layers,
        "dim_transformer": spec.dim_transformer,
        "nhead": spec.nhead,
        "dim_feedforward": spec.dim_feedforward,
        "diffusion_step": 1000,
        "timestep_type": TimestepEmbeddingType.adalayernorm,
    }

constraints

Aesthetic post-processing constraints for LACE layout boxes.

beautify_layout

beautify_layout(
    bbox: Float[Tensor, "batch elements 4"],
    mask: Bool[Tensor, "batch elements"],
    overlap_weight: float = 1.0,
    alignment_weight: float = 1.0,
    xy_only: bool = False,
    num_steps: int = 1000,
    lr: float = 0.0001,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Bool[torch.Tensor, "batch elements"],
]

Optimize generated boxes with overlap and alignment penalties.

Parameters:

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

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

required
mask Bool[Tensor, 'batch elements']

Valid-element mask with shape (batch, seq).

required
overlap_weight float

Weight for pairwise overlap penalties.

1.0
alignment_weight float

Weight for alignment penalties.

1.0
xy_only bool

Whether to keep width and height fixed.

False
num_steps int

Number of Adam optimization steps.

1000
lr float

Adam learning rate.

0.0001

Returns:

Type Description
tuple[Float[Tensor, 'batch elements 4'], Bool[Tensor, 'batch elements']]

Optimized boxes and updated mask.

Source code in models/lace/src/lace/constraints.py
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def beautify_layout(
    bbox: Float[torch.Tensor, "batch elements 4"],
    mask: Bool[torch.Tensor, "batch elements"],
    overlap_weight: float = 1.0,
    alignment_weight: float = 1.0,
    xy_only: bool = False,
    num_steps: int = 1000,
    lr: float = 1e-4,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Bool[torch.Tensor, "batch elements"],
]:
    """Optimize generated boxes with overlap and alignment penalties.

    Args:
        bbox: Normalized center ``xywh`` boxes with shape ``(batch, seq, 4)``.
        mask: Valid-element mask with shape ``(batch, seq)``.
        overlap_weight: Weight for pairwise overlap penalties.
        alignment_weight: Weight for alignment penalties.
        xy_only: Whether to keep width and height fixed.
        num_steps: Number of Adam optimization steps.
        lr: Adam learning rate.

    Returns:
        Optimized boxes and updated mask.
    """
    if torch.sum(mask) == 1:
        return bbox, mask

    bbox_in = bbox
    if xy_only:
        wh = torch.abs(bbox[:, :, 2:].clone().detach())
        bbox_in = torch.cat([bbox[:, :, :2], wh], dim=2)

    bbox_in = bbox_in.clone()
    bbox_in[:, :, [0, 2]] *= 10 / 4
    bbox_in[:, :, [1, 3]] *= 10 / 6
    bbox_initial = bbox_in.clone().detach()
    bbox_param = nn.Parameter(bbox_in)
    optimizer = torch.optim.Adam([bbox_param], lr=lr)
    mse_loss = nn.MSELoss()
    mask_out = mask.clone()

    with torch.enable_grad():
        for _ in range(num_steps):
            bbox_relu = torch.relu(bbox_param)
            align_score = _layout_alignment_matrix(bbox_relu, mask_out)
            align_mask = (align_score < 1 / 64).clone().detach()
            align_loss = torch.mean(align_score * align_mask)
            piou = torch.mean(_pairwise_iou_xywh(bbox_relu, mask_out))
            mse = mse_loss(bbox_relu, bbox_initial)
            loss = mse + alignment_weight * align_loss + overlap_weight * piou
            optimizer.zero_grad()
            loss.backward()
            torch.nn.utils.clip_grad_norm_([bbox_param], 1.0)
            optimizer.step()
            min_wh = torch.min(bbox_relu[:, :, [2, 3]], dim=2).values
            mask_out = mask_out * (min_wh > 0.01)

    bbox_out = torch.relu(bbox_param.detach())
    bbox_out[:, :, [0, 2]] *= 4 / 10
    bbox_out[:, :, [1, 3]] *= 6 / 10
    return bbox_out, mask_out

conversion

Checkpoint conversion helpers for vendor LACE weights.

load_vendor_state_dict

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

Load a PyTorch checkpoint as a state dictionary.

Parameters:

Name Type Description Default
path str | Path

Path to a vendor checkpoint or state-dict file.

required

Returns:

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

Mapping from parameter names to tensors.

Raises:

Type Description
TypeError

If the checkpoint does not contain a state-dict-like object.

Source code in models/lace/src/lace/conversion.py
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
def load_vendor_state_dict(path: str | Path) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load a PyTorch checkpoint as a state dictionary.

    Args:
        path: Path to a vendor checkpoint or state-dict file.

    Returns:
        Mapping from parameter names to tensors.

    Raises:
        TypeError: If the checkpoint does not contain a state-dict-like object.
    """
    loaded = torch.load(path, map_location="cpu")
    if isinstance(loaded, dict) and "state_dict" in loaded:
        loaded = loaded["state_dict"]
    if not isinstance(loaded, dict):
        raise TypeError(f"Expected a state_dict-like checkpoint at {path}")

    return dict(loaded)

convert_state_dict

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

Convert vendor parameter names to the Diffusers module names.

Parameters:

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

Vendor state dictionary.

required

Returns:

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

Converted state dictionary with distributed prefixes and vendor

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

positional buffers removed.

Source code in models/lace/src/lace/conversion.py
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def convert_state_dict(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Convert vendor parameter names to the Diffusers module names.

    Args:
        state_dict: Vendor state dictionary.

    Returns:
        Converted state dictionary with distributed prefixes and vendor
        positional buffers removed.
    """
    return {
        key.removeprefix("module."): value
        for key, value in state_dict.items()
        if key != "pos_embed" and key.removeprefix("module.") != "pos_embed"
    }

build_pipeline_from_vendor_checkpoint

build_pipeline_from_vendor_checkpoint(
    dataset: DatasetName | str,
    checkpoint_path: str | Path,
    ddim_num_steps: int = 100,
) -> LacePipeline

Build a LACE pipeline from a vendor checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required
checkpoint_path str | Path

Path to the vendor checkpoint.

required
ddim_num_steps int

Number of DDIM inference steps configured on the scheduler.

100

Returns:

Type Description
LacePipeline

Pipeline containing converted model, scheduler, and processor.

Raises:

Type Description
TypeError

If the checkpoint payload is not a state dictionary.

ValueError

If converted keys do not match the model architecture.

Source code in models/lace/src/lace/conversion.py
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def build_pipeline_from_vendor_checkpoint(
    dataset: DatasetName | str,
    checkpoint_path: str | Path,
    ddim_num_steps: int = 100,
) -> LacePipeline:
    """Build a LACE pipeline from a vendor checkpoint.

    Args:
        dataset: LACE dataset name or alias.
        checkpoint_path: Path to the vendor checkpoint.
        ddim_num_steps: Number of DDIM inference steps configured on the scheduler.

    Returns:
        Pipeline containing converted model, scheduler, and processor.

    Raises:
        TypeError: If the checkpoint payload is not a state dictionary.
        ValueError: If converted keys do not match the model architecture.
    """
    model = LaceTransformerModel(**default_model_config(dataset))
    converted = convert_state_dict(load_vendor_state_dict(checkpoint_path))
    expected = set(model.state_dict())
    actual = set(converted)
    missing = sorted(expected - actual)
    unexpected = sorted(actual - expected)
    if missing or unexpected:
        raise ValueError(
            f"State dict mismatch: missing={missing[:10]}, unexpected={unexpected[:10]}"
        )

    model.load_state_dict(converted, strict=True)
    processor = LaceProcessor.from_dataset(dataset)
    scheduler = LaceScheduler(ddim_num_steps=ddim_num_steps)
    return LacePipeline(model=model, scheduler=scheduler, processor=processor)

model_card

Model-card generation for converted LACE checkpoints.

lace_model_card

lace_model_card(
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> ModelCard

Create the Hugging Face model card for a LACE checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required
parity_metrics list[ParityMetric] | None

Optional parity metrics to embed in the evaluation section.

None

Returns:

Type Description
ModelCard

Rendered Hugging Face Hub model card.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Source code in models/lace/src/lace/model_card.py
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def lace_model_card(
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> ModelCard:
    """Create the Hugging Face model card for a LACE checkpoint.

    Args:
        dataset: LACE dataset name or alias.
        parity_metrics: Optional parity metrics to embed in the evaluation section.

    Returns:
        Rendered Hugging Face Hub model card.

    Raises:
        ValueError: If the dataset is unsupported.
    """
    dataset_key = normalize_dataset(dataset)
    dataset_name = str(dataset_key)
    model_id = _MODEL_IDS[dataset_key]
    metrics = parity_metrics
    if metrics is None:
        metrics = (
            [_PARITY_METRICS[dataset_key]] if dataset_key in _PARITY_METRICS else []
        )
    how_to_use = f"""
from lace import LacePipeline

pipe = LacePipeline.from_pretrained("{model_id}")
out = pipe(batch_size=1, seed=0, num_inference_steps=100)
print(out.bbox, out.labels, out.mask)
"""
    details = (
        "Diffusers-format conversion of the LACE checkpoint for "
        f"`{dataset_name}`. LACE is a continuous diffusion layout generation model "
        "from the paper 'Towards Aligned Layout Generation via Diffusion Model "
        "with Aesthetic Constraints' (https://arxiv.org/abs/2402.04754). "
        "The pipeline generates normalized center `xywh` layout boxes, "
        "category labels, and masks."
    )
    if dataset_key is DatasetName.rico13:
        details += (
            " The public checkpoint archive used for local parity "
            "verification does not include `rico13_best.pt`; Rico13 parity "
            "metrics are therefore not reported here."
        )
    return build_layout_model_card(
        model_id=model_id,
        model_name=f"LACE {dataset_name}",
        dataset_ids=[_DATASET_IDS[dataset_key]],
        license="mit",
        library_name="diffusers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "lace",
            "diffusers",
            dataset_name,
        ],
        model_details=details,
        intended_uses=(
            "Use this checkpoint for research and evaluation of document and UI "
            "layout generation workflows."
        ),
        limitations=(
            "The converted checkpoint follows the original LACE release and is "
            "intended for layout synthesis, not for image rendering or OCR. "
            "Generated layouts may require downstream filtering for task-specific "
            "aesthetic or overlap constraints."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{_DATASET_IDS[dataset_key]}` "
            "as released by the original LACE project."
        ),
        parity_metrics=metrics,
        citation_bibtex=_LACE_BIBTEX,
        original_implementation_url="https://github.com/puar-playground/LACE",
    )

write_lace_model_card

write_lace_model_card(
    output_dir: str | Path,
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> Path

Write the LACE model card into a model directory.

Parameters:

Name Type Description Default
output_dir str | Path

Directory where README.md is written.

required
dataset DatasetName | str

LACE dataset name or alias.

required
parity_metrics list[ParityMetric] | None

Optional parity metrics to embed in the evaluation section.

None

Returns:

Type Description
Path

Path to the written README.md.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Source code in models/lace/src/lace/model_card.py
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def write_lace_model_card(
    output_dir: str | Path,
    dataset: DatasetName | str,
    *,
    parity_metrics: list[ParityMetric] | None = None,
) -> Path:
    """Write the LACE model card into a model directory.

    Args:
        output_dir: Directory where ``README.md`` is written.
        dataset: LACE dataset name or alias.
        parity_metrics: Optional parity metrics to embed in the evaluation section.

    Returns:
        Path to the written ``README.md``.

    Raises:
        ValueError: If the dataset is unsupported.
    """
    path = Path(output_dir) / "README.md"
    path.write_text(
        str(lace_model_card(dataset, parity_metrics=parity_metrics)),
        encoding="utf-8",
    )
    return path

modeling_lace

Transformer denoiser used by converted LACE checkpoints.

ActivationFn

Bases: Protocol

Callable activation function for tensor-valued feed-forward blocks.

Source code in lib/laygen/src/laygen/nn/activations.py
31
32
33
34
35
36
37
38
39
@runtime_checkable
class ActivationFn(Protocol):
    """Callable activation function for tensor-valued feed-forward blocks."""

    def __call__(
        self,
        input: Float[torch.Tensor, ...],
    ) -> Float[torch.Tensor, ...]:
        """Apply the activation to a tensor."""

__call__

__call__(
    input: Float[Tensor, ...],
) -> Float[torch.Tensor, ...]

Apply the activation to a tensor.

Source code in lib/laygen/src/laygen/nn/activations.py
35
36
37
38
39
def __call__(
    self,
    input: Float[torch.Tensor, ...],
) -> Float[torch.Tensor, ...]:
    """Apply the activation to a tensor."""

ActivationName

Bases: StrEnum

Supported feed-forward activation names.

Origin

gelu2 is the VQ-Diffusion GELU2/QuickGELU branch used by the LayoutDM, LACE, and LayoutFlow transformer utilities.

Source code in lib/laygen/src/laygen/nn/activations.py
18
19
20
21
22
23
24
25
26
27
28
class ActivationName(StrEnum):
    """Supported feed-forward activation names.

    Origin:
        ``gelu2`` is the VQ-Diffusion GELU2/QuickGELU branch used by the
        LayoutDM, LACE, and LayoutFlow transformer utilities.
    """

    relu = auto()
    gelu = auto()
    gelu2 = auto()

AdaInsNorm

Bases: _AdaNorm

Adaptive instance normalization conditioned on diffusion timestep.

Origin

This module follows VQ-Diffusion AdaInsNorm as used by the LACE checkpoint backbone; Diffusers has no key-compatible AdaInstanceNorm path.

Parameters:

Name Type Description Default
n_embd int

Hidden dimension.

required
max_timestep int

Maximum diffusion timestep.

required
emb_type TimestepEmbeddingType | str

Timestep embedding variant.

adalayernorm_abs
Source code in lib/laygen/src/laygen/nn/norms.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
class AdaInsNorm(_AdaNorm):
    """Adaptive instance normalization conditioned on diffusion timestep.

    Origin:
        This module follows VQ-Diffusion ``AdaInsNorm`` as used by the LACE
        checkpoint backbone; Diffusers has no key-compatible AdaInstanceNorm path.

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

    def __init__(
        self,
        n_embd: int,
        max_timestep: int,
        emb_type: TimestepEmbeddingType | str = TimestepEmbeddingType.adalayernorm_abs,
    ) -> None:
        """Initialize adaptive instance normalization."""
        super().__init__(n_embd, max_timestep, emb_type)
        self.instancenorm = nn.InstanceNorm1d(n_embd)

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

__init__

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

Initialize adaptive instance normalization.

Source code in lib/laygen/src/laygen/nn/norms.py
89
90
91
92
93
94
95
96
97
def __init__(
    self,
    n_embd: int,
    max_timestep: int,
    emb_type: TimestepEmbeddingType | str = TimestepEmbeddingType.adalayernorm_abs,
) -> None:
    """Initialize adaptive instance normalization."""
    super().__init__(n_embd, max_timestep, emb_type)
    self.instancenorm = nn.InstanceNorm1d(n_embd)

forward

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

Apply timestep-conditioned instance normalization.

Source code in lib/laygen/src/laygen/nn/norms.py
 99
100
101
102
103
104
105
106
107
108
109
110
def forward(
    self,
    x: Float[torch.Tensor, "batch tokens channels"],
    timestep: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Apply timestep-conditioned instance normalization."""
    emb = self.linear(self.silu(self.emb(timestep))).unsqueeze(1)
    scale, shift = torch.chunk(emb, 2, dim=2)
    return (
        self.instancenorm(x.transpose(-1, -2)).transpose(-1, -2) * (1 + scale)
        + shift
    )

AdaLayerNorm

Bases: _AdaNorm

Adaptive layer normalization conditioned on diffusion timestep.

Origin

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

Parameters:

Name Type Description Default
n_embd int

Hidden dimension.

required
max_timestep int

Maximum diffusion timestep.

required
emb_type TimestepEmbeddingType | str

Timestep embedding variant.

adalayernorm_abs
Source code in lib/laygen/src/laygen/nn/norms.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
class AdaLayerNorm(_AdaNorm):
    """Adaptive layer normalization conditioned on diffusion timestep.

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

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

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

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

__init__

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

Initialize adaptive layer normalization.

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

forward

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

Apply timestep-conditioned layer normalization.

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

SinusoidalPosEmb

Bases: Module

Sinusoidal timestep or position embedding.

Origin

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

Parameters:

Name Type Description Default
num_steps int

Maximum number of positions or timesteps.

required
dim int

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

required
rescale_steps int

Rescaling constant used by the released checkpoints.

4000
Source code in lib/laygen/src/laygen/nn/embeddings.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
class SinusoidalPosEmb(nn.Module):
    """Sinusoidal timestep or position embedding.

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

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

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

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

        Args:
            x: One-dimensional tensor of positions.

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

__init__

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

Initialize the embedding parameters.

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

forward

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

Embed integer positions or timesteps.

Parameters:

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

One-dimensional tensor of positions.

required

Returns:

Type Description
Float[Tensor, 'batch channels']

Sinusoidal embedding tensor.

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

    Args:
        x: One-dimensional tensor of positions.

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

TimestepEmbeddingType

Bases: StrEnum

Supported timestep-conditioned normalization variants.

Origin

These names come from VQ-Diffusion-derived adaptive normalization modes used by the LayoutDM and LACE checkpoint backbones.

Source code in lib/laygen/src/laygen/nn/embeddings.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class TimestepEmbeddingType(StrEnum):
    """Supported timestep-conditioned normalization variants.

    Origin:
        These names come from VQ-Diffusion-derived adaptive normalization modes
        used by the LayoutDM and LACE checkpoint backbones.
    """

    adalayernorm = auto()
    adainnorm = auto()
    adalayernorm_abs = auto()
    adainnorm_abs = auto()
    adalayernorm_mlp = auto()
    adainnorm_mlp = auto()

LaceModelOutput dataclass

Bases: BaseOutput

Output returned by the LACE transformer.

Attributes:

Name Type Description
sample Float[Tensor, 'batch elements channels']

Predicted noise tensor with the same shape as the input sample.

Source code in models/lace/src/lace/modeling_lace.py
46
47
48
49
50
51
52
53
54
@dataclass
class LaceModelOutput(BaseOutput):
    """Output returned by the LACE transformer.

    Attributes:
        sample: Predicted noise tensor with the same shape as the input sample.
    """

    sample: Float[torch.Tensor, "batch elements channels"]

LaceTransformerModel

Bases: ModelMixin, ConfigMixin

Transformer denoiser for continuous LACE layout tensors.

Parameters:

Name Type Description Default
seq_dim int

Number of channels per layout element.

required
max_seq_length int

Maximum number of layout elements.

25
num_layers int

Number of transformer blocks.

4
dim_transformer int

Hidden dimension.

512
nhead int

Number of attention heads.

16
dim_feedforward int

Feed-forward hidden dimension.

2048
diffusion_step int

Maximum diffusion timestep.

1000
timestep_type TimestepEmbeddingType | str | None

Timestep-conditioned normalization variant.

adalayernorm
Source code in models/lace/src/lace/modeling_lace.py
 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
class LaceTransformerModel(ModelMixin, ConfigMixin):
    """Transformer denoiser for continuous LACE layout tensors.

    Args:
        seq_dim: Number of channels per layout element.
        max_seq_length: Maximum number of layout elements.
        num_layers: Number of transformer blocks.
        dim_transformer: Hidden dimension.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden dimension.
        diffusion_step: Maximum diffusion timestep.
        timestep_type: Timestep-conditioned normalization variant.
    """

    config_name = "model_config.json"

    pos_embed: Float[torch.Tensor, "max_seq_length dim_transformer"]

    @register_to_config
    def __init__(
        self,
        *,
        seq_dim: int,
        max_seq_length: int = 25,
        num_layers: int = 4,
        dim_transformer: int = 512,
        nhead: int = 16,
        dim_feedforward: int = 2048,
        diffusion_step: int = 1000,
        timestep_type: TimestepEmbeddingType
        | str
        | None = TimestepEmbeddingType.adalayernorm,
    ) -> None:
        """Initialize a LACE transformer denoiser."""
        super().__init__()
        self.seq_dim = seq_dim
        self.max_seq_length = max_seq_length
        self.pos_encoder = SinusoidalPosEmb(max_seq_length, dim_transformer)
        pos_i = torch.arange(max_seq_length)
        self.register_buffer("pos_embed", self.pos_encoder(pos_i), persistent=False)
        self.layer_in = nn.Linear(seq_dim, dim_transformer)
        encoder_layer = Block(
            d_model=dim_transformer,
            nhead=nhead,
            dim_feedforward=dim_feedforward,
            diffusion_step=diffusion_step,
            timestep_type=timestep_type,
        )
        self.layers = clone_module_list(encoder_layer, num_layers)
        self.num_layers = num_layers
        self.layer_out = nn.Linear(dim_transformer, seq_dim)

    def forward(
        self,
        sample: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        attention_mask: Bool[torch.Tensor, "batch elements"] | None = None,
        return_dict: bool = True,
    ) -> LaceModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
        """Predict denoising residuals for a layout sample.

        Args:
            sample: Noisy layout tensor.
            timestep: Diffusion timestep per sample.
            attention_mask: Optional valid-element mask.
            return_dict: Whether to return ``LaceModelOutput``.

        Returns:
            Output dataclass or a one-item tuple containing the prediction.
        """
        output = F.softplus(self.layer_in(sample))
        pos_i = torch.arange(output.shape[1], device=output.device)
        output = output + self.pos_encoder(pos_i).to(output)
        key_padding_mask = None if attention_mask is None else ~attention_mask.bool()
        for i, layer in enumerate(self.layers):
            output = layer(
                output,
                src_key_padding_mask=key_padding_mask,
                timestep=timestep,
            )
            if i < self.num_layers - 1:
                output = F.softplus(output)
        output = self.layer_out(output)
        if not return_dict:
            return (output,)
        return LaceModelOutput(sample=output)

__init__

__init__(
    *,
    seq_dim: int,
    max_seq_length: int = 25,
    num_layers: int = 4,
    dim_transformer: int = 512,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    diffusion_step: int = 1000,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None

Initialize a LACE transformer denoiser.

Source code in models/lace/src/lace/modeling_lace.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
@register_to_config
def __init__(
    self,
    *,
    seq_dim: int,
    max_seq_length: int = 25,
    num_layers: int = 4,
    dim_transformer: int = 512,
    nhead: int = 16,
    dim_feedforward: int = 2048,
    diffusion_step: int = 1000,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None:
    """Initialize a LACE transformer denoiser."""
    super().__init__()
    self.seq_dim = seq_dim
    self.max_seq_length = max_seq_length
    self.pos_encoder = SinusoidalPosEmb(max_seq_length, dim_transformer)
    pos_i = torch.arange(max_seq_length)
    self.register_buffer("pos_embed", self.pos_encoder(pos_i), persistent=False)
    self.layer_in = nn.Linear(seq_dim, dim_transformer)
    encoder_layer = Block(
        d_model=dim_transformer,
        nhead=nhead,
        dim_feedforward=dim_feedforward,
        diffusion_step=diffusion_step,
        timestep_type=timestep_type,
    )
    self.layers = clone_module_list(encoder_layer, num_layers)
    self.num_layers = num_layers
    self.layer_out = nn.Linear(dim_transformer, seq_dim)

forward

forward(
    sample: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    attention_mask: Bool[Tensor, "batch elements"]
    | None = None,
    return_dict: bool = True,
) -> (
    LaceModelOutput
    | tuple[Float[torch.Tensor, "batch elements channels"]]
)

Predict denoising residuals for a layout sample.

Parameters:

Name Type Description Default
sample Float[Tensor, 'batch elements channels']

Noisy layout tensor.

required
timestep Int[Tensor, 'batch']

Diffusion timestep per sample.

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

Optional valid-element mask.

None
return_dict bool

Whether to return LaceModelOutput.

True

Returns:

Type Description
LaceModelOutput | tuple[Float[Tensor, 'batch elements channels']]

Output dataclass or a one-item tuple containing the prediction.

Source code in models/lace/src/lace/modeling_lace.py
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
def forward(
    self,
    sample: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    attention_mask: Bool[torch.Tensor, "batch elements"] | None = None,
    return_dict: bool = True,
) -> LaceModelOutput | tuple[Float[torch.Tensor, "batch elements channels"]]:
    """Predict denoising residuals for a layout sample.

    Args:
        sample: Noisy layout tensor.
        timestep: Diffusion timestep per sample.
        attention_mask: Optional valid-element mask.
        return_dict: Whether to return ``LaceModelOutput``.

    Returns:
        Output dataclass or a one-item tuple containing the prediction.
    """
    output = F.softplus(self.layer_in(sample))
    pos_i = torch.arange(output.shape[1], device=output.device)
    output = output + self.pos_encoder(pos_i).to(output)
    key_padding_mask = None if attention_mask is None else ~attention_mask.bool()
    for i, layer in enumerate(self.layers):
        output = layer(
            output,
            src_key_padding_mask=key_padding_mask,
            timestep=timestep,
        )
        if i < self.num_layers - 1:
            output = F.softplus(output)
    output = self.layer_out(output)
    if not return_dict:
        return (output,)
    return LaceModelOutput(sample=output)

normalize_activation

normalize_activation(
    name: ActivationName | str | ActivationFn,
) -> ActivationName | ActivationFn

Normalize an activation name while preserving custom callables.

Origin

The closed string set keeps the activation names used by VQ-Diffusion-derived LayoutDM, LACE, and LayoutFlow backbones.

Parameters:

Name Type Description Default
name ActivationName | str | ActivationFn

Activation enum, string value, or callable.

required

Returns:

Type Description
ActivationName | ActivationFn

Canonical activation enum or the original callable.

Raises:

Type Description
ValueError

If the activation name is unsupported.

Source code in lib/laygen/src/laygen/nn/activations.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
def normalize_activation(
    name: ActivationName | str | ActivationFn,
) -> ActivationName | ActivationFn:
    """Normalize an activation name while preserving custom callables.

    Origin:
        The closed string set keeps the activation names used by
        VQ-Diffusion-derived LayoutDM, LACE, and LayoutFlow backbones.

    Args:
        name: Activation enum, string value, or callable.

    Returns:
        Canonical activation enum or the original callable.

    Raises:
        ValueError: If the activation name is unsupported.
    """
    if callable(name):
        return cast(ActivationFn, name)
    try:
        return ActivationName(name)
    except ValueError as exc:
        raise ValueError(f"Unsupported activation: {name}") from exc

normalize_timestep_embedding

normalize_timestep_embedding(
    timestep_type: TimestepEmbeddingType | str | None,
) -> TimestepEmbeddingType | None

Normalize a timestep embedding mode.

Origin

This normalizes the VQ-Diffusion-derived adaptive normalization mode names exposed by LayoutDM and LACE checkpoints.

Parameters:

Name Type Description Default
timestep_type TimestepEmbeddingType | str | None

Embedding enum, string value, or None.

required

Returns:

Type Description
TimestepEmbeddingType | None

Canonical embedding enum or None.

Raises:

Type Description
ValueError

If the embedding mode is unsupported.

Source code in lib/laygen/src/laygen/nn/embeddings.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def normalize_timestep_embedding(
    timestep_type: TimestepEmbeddingType | str | None,
) -> TimestepEmbeddingType | None:
    """Normalize a timestep embedding mode.

    Origin:
        This normalizes the VQ-Diffusion-derived adaptive normalization mode
        names exposed by LayoutDM and LACE checkpoints.

    Args:
        timestep_type: Embedding enum, string value, or ``None``.

    Returns:
        Canonical embedding enum or ``None``.

    Raises:
        ValueError: If the embedding mode is unsupported.
    """
    if timestep_type is None or isinstance(timestep_type, TimestepEmbeddingType):
        return timestep_type
    try:
        return TimestepEmbeddingType(timestep_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported timestep_type: {timestep_type}") from exc

pipeline_lace

Diffusers pipeline for LACE layout generation.

PipelineOutputType

Bases: StrEnum

Supported LACE pipeline output containers.

Source code in models/lace/src/lace/pipeline_lace.py
32
33
34
35
36
class PipelineOutputType(StrEnum):
    """Supported LACE pipeline output containers."""

    dataclass = auto()
    dict = auto()

LaceConditionAlias

Bases: StrEnum

LACE-specific public condition aliases not in the shared registry.

Source code in models/lace/src/lace/pipeline_lace.py
39
40
41
42
43
44
class LaceConditionAlias(StrEnum):
    """LACE-specific public condition aliases not in the shared registry."""

    none = auto()
    category = auto()
    label_size_plus = "label+size"

LacePipeline

Bases: DiffusionPipeline

Generate layouts with a converted LACE checkpoint.

Parameters:

Name Type Description Default
model LaceTransformerModel

LACE transformer denoiser.

required
scheduler LaceScheduler

DDIM-style scheduler.

required
processor LaceProcessor

Processor that encodes and decodes layout tensors.

required

Examples:

>>> from lace import LaceProcessor, LaceScheduler, LaceTransformerModel
>>> model = LaceTransformerModel(seq_dim=10, max_seq_length=2, num_layers=1, dim_transformer=8, nhead=2, dim_feedforward=16)
>>> pipe = LacePipeline(model=model, scheduler=LaceScheduler(ddim_num_steps=1), processor=LaceProcessor.from_dataset("publaynet"))
>>> pipe.processor.max_seq_length
25
Source code in models/lace/src/lace/pipeline_lace.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
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
class LacePipeline(DiffusionPipeline):
    """Generate layouts with a converted LACE checkpoint.

    Args:
        model: LACE transformer denoiser.
        scheduler: DDIM-style scheduler.
        processor: Processor that encodes and decodes layout tensors.

    Examples:
        >>> from lace import LaceProcessor, LaceScheduler, LaceTransformerModel
        >>> model = LaceTransformerModel(seq_dim=10, max_seq_length=2, num_layers=1, dim_transformer=8, nhead=2, dim_feedforward=16)
        >>> pipe = LacePipeline(model=model, scheduler=LaceScheduler(ddim_num_steps=1), processor=LaceProcessor.from_dataset("publaynet"))
        >>> pipe.processor.max_seq_length
        25
    """

    model_cpu_offload_seq = "model"
    _optional_components = ["processor"]

    def __init__(
        self,
        model: LaceTransformerModel,
        scheduler: LaceScheduler,
        processor: LaceProcessor,
    ) -> None:
        """Attach the converted LACE denoiser, scheduler, and processor."""
        super().__init__()
        self.register_modules(model=model, scheduler=scheduler)
        self.model = model
        self.scheduler = scheduler
        self.processor = processor
        self.model.eval()

    @property
    def components(
        self,
    ) -> dict[str, LaceTransformerModel | LaceScheduler | LaceProcessor]:
        """Expose modules and processor metadata for Diffusers serialization."""
        return dict(
            model=self.model, scheduler=self.scheduler, processor=self.processor
        )

    @torch.no_grad()
    def __call__(
        self,
        *,
        batch_size: int = 1,
        num_inference_steps: int | None = None,
        generator: torch.Generator | None = None,
        seed: int | None = None,
        condition_type: ConditionType | str | None = ConditionType.unconditional,
        bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
        labels: Int[torch.Tensor, "batch elements"] | None = None,
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        box_format: BoxFormat | str = BoxFormat.xywh,
        normalized: bool = True,
        canvas_size: tuple[int, int] | None = None,
        completion_ratio: float = 0.2,
        refinement_noise: float = 0.1,
        beautify: bool = False,
        beautify_overlap_weight: float | None = None,
        beautify_alignment_weight: float = 1.0,
        output_type: PipelineOutputType | str = PipelineOutputType.dataclass,
        return_intermediates: bool = False,
    ) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
        """Run LACE denoising and return generated layouts.

        Args:
            batch_size: Number of layouts to generate for unconditional calls.
            num_inference_steps: Number of DDIM steps. Uses scheduler default if
                omitted.
            generator: Optional torch generator. Takes precedence over ``seed``.
            seed: Convenience seed used only when ``generator`` is absent.
            condition_type: Conditioning mode or alias.
            bbox: Conditioning boxes for non-unconditional modes.
            labels: Conditioning labels for non-unconditional modes.
            mask: Optional conditioning mask.
            box_format: Input box format for conditioning boxes.
            normalized: Whether conditioning boxes are normalized.
            canvas_size: Pixel canvas size required when ``normalized`` is false.
            completion_ratio: Maximum random completion fraction.
            refinement_noise: Noise scale for refinement conditioning.
            beautify: Whether to run the aesthetic post-optimization.
            beautify_overlap_weight: Optional overlap penalty override.
            beautify_alignment_weight: Alignment penalty weight.
            output_type: ``"dataclass"`` or ``"dict"``.
            return_intermediates: Whether to return the denoising trajectory.

        Returns:
            Layout output dataclass or a dictionary.

        Raises:
            ValueError: If a condition/output mode is unsupported or required
                conditioning tensors are missing.
        """
        if generator is None and seed is not None:
            generator = torch.Generator(device=self.device).manual_seed(seed)
        canonical = normalize_condition_type(condition_type)
        encoded = None
        if canonical is not ConditionType.unconditional:
            if bbox is None or labels is None:
                raise ValueError(
                    f"bbox and labels are required for condition_type={condition_type}"
                )

            encoded = self.processor(
                bbox=bbox,
                labels=labels,
                mask=mask,
                box_format=box_format,
                normalized=normalized,
                canvas_size=canvas_size,
            )
            batch_size = encoded[LACE_LAYOUT_KEY].shape[0]
        self.scheduler.set_timesteps(num_inference_steps, device=self.device)
        sample = self.scheduler.initial_sample(
            batch_size,
            self.processor.max_seq_length,
            self.processor.seq_dim,
            device=self.device,
            generator=generator,
        )
        if canonical is ConditionType.refinement:
            assert encoded is not None
            noise = torch.randn(
                encoded[LACE_BBOX_KEY].shape,
                dtype=encoded[LACE_BBOX_KEY].dtype,
                device=encoded[LACE_BBOX_KEY].device,
                generator=generator,
            )
            noisy_bbox = (encoded[LACE_BBOX_KEY] + refinement_noise * noise).clamp(0, 1)
            sample = self.processor.encode(
                noisy_bbox.to(self.device),
                encoded[LACE_LABELS_KEY].to(self.device),
                encoded[LACE_MASK_KEY].to(self.device),
            )
        real_layout = (
            None if encoded is None else encoded[LACE_LAYOUT_KEY].to(self.device)
        )
        fix_mask = self._build_fix_mask(
            canonical, real_layout, completion_ratio, generator
        )
        trajectory = [] if return_intermediates else None
        step_indices = (
            self.scheduler.refinement_indices()
            if canonical is ConditionType.refinement
            else list(range(len(self.scheduler.timesteps) - 1, -1, -1))
        )
        for index in step_indices:
            step = self.scheduler.ddim_timesteps[index]
            timestep = torch.full(
                (batch_size,), int(step.item()), device=self.device, dtype=torch.long
            )
            if real_layout is not None and fix_mask is not None:
                sample[fix_mask] = real_layout[fix_mask]
            model_output = self.model(sample=sample, timestep=timestep).sample
            out = self.scheduler.step(
                model_output, timestep, sample, index=index, generator=generator
            )
            sample = out.prev_sample
            if real_layout is not None and fix_mask is not None:
                sample[fix_mask] = real_layout[fix_mask]
            if trajectory is not None:
                trajectory.append(sample.detach().cpu())
        decoded = self.processor.decode(sample.detach().cpu())
        bbox_out = decoded.bbox
        mask_out = decoded.mask
        if beautify:
            overlap = beautify_overlap_weight
            if overlap is None:
                overlap = (
                    1.0
                    if normalize_dataset(self.processor.dataset)
                    is DatasetName.publaynet
                    else 0.0
                )
            bbox_out, mask_out = beautify_layout(
                bbox_out,
                mask_out,
                overlap_weight=overlap,
                alignment_weight=beautify_alignment_weight,
            )
        output = LayoutGenerationOutput(
            bbox=bbox_out,
            labels=decoded.labels,
            mask=mask_out,
            id2label=decoded.id2label,
            trajectory=trajectory,
            intermediates={"condition_type": str(canonical)}
            if return_intermediates
            else None,
        )
        out_type = normalize_output_type(output_type)
        if out_type is PipelineOutputType.dict:
            return dict(output)
        if out_type is PipelineOutputType.dataclass:
            return output
        assert_never(out_type)

    generate = __call__

    def save_pretrained(self, save_directory: str | Path) -> None:
        """Save pipeline components.

        Args:
            save_directory: Output directory.
        """
        super().save_pretrained(save_directory)
        self.processor.save_pretrained(save_directory)

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        processor: LaceProcessor | None = None,
    ) -> "LacePipeline":
        """Load a saved LACE pipeline.

        Args:
            pretrained_model_name_or_path: Local path or Hub id.
            processor: Optional processor override.

        Returns:
            Loaded pipeline with the serialized processor attached.
        """
        loaded_processor = (
            LaceProcessor.from_pretrained(pretrained_model_name_or_path)
            if processor is None
            else processor
        )
        pipe = super().from_pretrained(pretrained_model_name_or_path)
        pipe.processor = loaded_processor
        return pipe

    def _build_fix_mask(
        self,
        condition_type: ConditionType,
        real_layout: Float[torch.Tensor, "batch elements channels"] | None,
        completion_ratio: float,
        generator: torch.Generator | None,
    ) -> Bool[torch.Tensor, "batch elements channels"] | None:
        """Build the fixed-channel mask for conditional generation."""
        if real_layout is None or condition_type is ConditionType.refinement:
            return None

        batch_size, seq_len, seq_dim = real_layout.shape
        num_class = seq_dim - 4

        if condition_type is ConditionType.label:
            fix_mask = torch.zeros_like(real_layout, dtype=torch.bool)
            fix_mask[:, :, :num_class] = True
            return fix_mask

        if condition_type is ConditionType.label_size:
            fix_mask = torch.zeros_like(real_layout, dtype=torch.bool)
            fix_indices = list(range(num_class)) + [num_class + 2, num_class + 3]
            fix_mask[:, :, fix_indices] = True
            return fix_mask

        if condition_type is ConditionType.completion:
            labels = real_layout[:, :, :num_class].argmax(dim=2)
            real_mask = labels != (num_class - 1)
            cutoff = torch.rand(
                (), device=real_layout.device, generator=generator
            ).item()
            element_mask = (
                torch.rand(
                    batch_size,
                    seq_len,
                    device=real_layout.device,
                    generator=generator,
                )
                <= cutoff * completion_ratio
            ) & real_mask
            return element_mask.unsqueeze(-1).expand(-1, -1, seq_dim)

        if condition_type is ConditionType.unconditional:
            return None
        raise ValueError(f"Unsupported LACE condition_type: {condition_type}")

components property

components: dict[
    str,
    LaceTransformerModel | LaceScheduler | LaceProcessor,
]

Expose modules and processor metadata for Diffusers serialization.

__init__

__init__(
    model: LaceTransformerModel,
    scheduler: LaceScheduler,
    processor: LaceProcessor,
) -> None

Attach the converted LACE denoiser, scheduler, and processor.

Source code in models/lace/src/lace/pipeline_lace.py
143
144
145
146
147
148
149
150
151
152
153
154
155
def __init__(
    self,
    model: LaceTransformerModel,
    scheduler: LaceScheduler,
    processor: LaceProcessor,
) -> None:
    """Attach the converted LACE denoiser, scheduler, and processor."""
    super().__init__()
    self.register_modules(model=model, scheduler=scheduler)
    self.model = model
    self.scheduler = scheduler
    self.processor = processor
    self.model.eval()

__call__

__call__(
    *,
    batch_size: int = 1,
    num_inference_steps: int | None = None,
    generator: Generator | None = None,
    seed: int | None = None,
    condition_type: ConditionType
    | str
    | None = ConditionType.unconditional,
    bbox: Float[Tensor, "batch elements 4"] | None = None,
    labels: Int[Tensor, "batch elements"] | None = None,
    mask: Bool[Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    completion_ratio: float = 0.2,
    refinement_noise: float = 0.1,
    beautify: bool = False,
    beautify_overlap_weight: float | None = None,
    beautify_alignment_weight: float = 1.0,
    output_type: PipelineOutputType
    | str = PipelineOutputType.dataclass,
    return_intermediates: bool = False,
) -> (
    LayoutGenerationOutput
    | dict[str, Shaped[torch.Tensor, "..."]]
)

Run LACE denoising and return generated layouts.

Parameters:

Name Type Description Default
batch_size int

Number of layouts to generate for unconditional calls.

1
num_inference_steps int | None

Number of DDIM steps. Uses scheduler default if omitted.

None
generator Generator | None

Optional torch generator. Takes precedence over seed.

None
seed int | None

Convenience seed used only when generator is absent.

None
condition_type ConditionType | str | None

Conditioning mode or alias.

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

Conditioning boxes for non-unconditional modes.

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

Conditioning labels for non-unconditional modes.

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

Optional conditioning mask.

None
box_format BoxFormat | str

Input box format for conditioning boxes.

xywh
normalized bool

Whether conditioning boxes are normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized is false.

None
completion_ratio float

Maximum random completion fraction.

0.2
refinement_noise float

Noise scale for refinement conditioning.

0.1
beautify bool

Whether to run the aesthetic post-optimization.

False
beautify_overlap_weight float | None

Optional overlap penalty override.

None
beautify_alignment_weight float

Alignment penalty weight.

1.0
output_type PipelineOutputType | str

"dataclass" or "dict".

dataclass
return_intermediates bool

Whether to return the denoising trajectory.

False

Returns:

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

Layout output dataclass or a dictionary.

Raises:

Type Description
ValueError

If a condition/output mode is unsupported or required conditioning tensors are missing.

Source code in models/lace/src/lace/pipeline_lace.py
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
@torch.no_grad()
def __call__(
    self,
    *,
    batch_size: int = 1,
    num_inference_steps: int | None = None,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    condition_type: ConditionType | str | None = ConditionType.unconditional,
    bbox: Float[torch.Tensor, "batch elements 4"] | None = None,
    labels: Int[torch.Tensor, "batch elements"] | None = None,
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    completion_ratio: float = 0.2,
    refinement_noise: float = 0.1,
    beautify: bool = False,
    beautify_overlap_weight: float | None = None,
    beautify_alignment_weight: float = 1.0,
    output_type: PipelineOutputType | str = PipelineOutputType.dataclass,
    return_intermediates: bool = False,
) -> LayoutGenerationOutput | dict[str, Shaped[torch.Tensor, "..."]]:
    """Run LACE denoising and return generated layouts.

    Args:
        batch_size: Number of layouts to generate for unconditional calls.
        num_inference_steps: Number of DDIM steps. Uses scheduler default if
            omitted.
        generator: Optional torch generator. Takes precedence over ``seed``.
        seed: Convenience seed used only when ``generator`` is absent.
        condition_type: Conditioning mode or alias.
        bbox: Conditioning boxes for non-unconditional modes.
        labels: Conditioning labels for non-unconditional modes.
        mask: Optional conditioning mask.
        box_format: Input box format for conditioning boxes.
        normalized: Whether conditioning boxes are normalized.
        canvas_size: Pixel canvas size required when ``normalized`` is false.
        completion_ratio: Maximum random completion fraction.
        refinement_noise: Noise scale for refinement conditioning.
        beautify: Whether to run the aesthetic post-optimization.
        beautify_overlap_weight: Optional overlap penalty override.
        beautify_alignment_weight: Alignment penalty weight.
        output_type: ``"dataclass"`` or ``"dict"``.
        return_intermediates: Whether to return the denoising trajectory.

    Returns:
        Layout output dataclass or a dictionary.

    Raises:
        ValueError: If a condition/output mode is unsupported or required
            conditioning tensors are missing.
    """
    if generator is None and seed is not None:
        generator = torch.Generator(device=self.device).manual_seed(seed)
    canonical = normalize_condition_type(condition_type)
    encoded = None
    if canonical is not ConditionType.unconditional:
        if bbox is None or labels is None:
            raise ValueError(
                f"bbox and labels are required for condition_type={condition_type}"
            )

        encoded = self.processor(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        batch_size = encoded[LACE_LAYOUT_KEY].shape[0]
    self.scheduler.set_timesteps(num_inference_steps, device=self.device)
    sample = self.scheduler.initial_sample(
        batch_size,
        self.processor.max_seq_length,
        self.processor.seq_dim,
        device=self.device,
        generator=generator,
    )
    if canonical is ConditionType.refinement:
        assert encoded is not None
        noise = torch.randn(
            encoded[LACE_BBOX_KEY].shape,
            dtype=encoded[LACE_BBOX_KEY].dtype,
            device=encoded[LACE_BBOX_KEY].device,
            generator=generator,
        )
        noisy_bbox = (encoded[LACE_BBOX_KEY] + refinement_noise * noise).clamp(0, 1)
        sample = self.processor.encode(
            noisy_bbox.to(self.device),
            encoded[LACE_LABELS_KEY].to(self.device),
            encoded[LACE_MASK_KEY].to(self.device),
        )
    real_layout = (
        None if encoded is None else encoded[LACE_LAYOUT_KEY].to(self.device)
    )
    fix_mask = self._build_fix_mask(
        canonical, real_layout, completion_ratio, generator
    )
    trajectory = [] if return_intermediates else None
    step_indices = (
        self.scheduler.refinement_indices()
        if canonical is ConditionType.refinement
        else list(range(len(self.scheduler.timesteps) - 1, -1, -1))
    )
    for index in step_indices:
        step = self.scheduler.ddim_timesteps[index]
        timestep = torch.full(
            (batch_size,), int(step.item()), device=self.device, dtype=torch.long
        )
        if real_layout is not None and fix_mask is not None:
            sample[fix_mask] = real_layout[fix_mask]
        model_output = self.model(sample=sample, timestep=timestep).sample
        out = self.scheduler.step(
            model_output, timestep, sample, index=index, generator=generator
        )
        sample = out.prev_sample
        if real_layout is not None and fix_mask is not None:
            sample[fix_mask] = real_layout[fix_mask]
        if trajectory is not None:
            trajectory.append(sample.detach().cpu())
    decoded = self.processor.decode(sample.detach().cpu())
    bbox_out = decoded.bbox
    mask_out = decoded.mask
    if beautify:
        overlap = beautify_overlap_weight
        if overlap is None:
            overlap = (
                1.0
                if normalize_dataset(self.processor.dataset)
                is DatasetName.publaynet
                else 0.0
            )
        bbox_out, mask_out = beautify_layout(
            bbox_out,
            mask_out,
            overlap_weight=overlap,
            alignment_weight=beautify_alignment_weight,
        )
    output = LayoutGenerationOutput(
        bbox=bbox_out,
        labels=decoded.labels,
        mask=mask_out,
        id2label=decoded.id2label,
        trajectory=trajectory,
        intermediates={"condition_type": str(canonical)}
        if return_intermediates
        else None,
    )
    out_type = normalize_output_type(output_type)
    if out_type is PipelineOutputType.dict:
        return dict(output)
    if out_type is PipelineOutputType.dataclass:
        return output
    assert_never(out_type)

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save pipeline components.

Parameters:

Name Type Description Default
save_directory str | Path

Output directory.

required
Source code in models/lace/src/lace/pipeline_lace.py
325
326
327
328
329
330
331
332
def save_pretrained(self, save_directory: str | Path) -> None:
    """Save pipeline components.

    Args:
        save_directory: Output directory.
    """
    super().save_pretrained(save_directory)
    self.processor.save_pretrained(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    processor: LaceProcessor | None = None,
) -> "LacePipeline"

Load a saved LACE pipeline.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Local path or Hub id.

required
processor LaceProcessor | None

Optional processor override.

None

Returns:

Type Description
'LacePipeline'

Loaded pipeline with the serialized processor attached.

Source code in models/lace/src/lace/pipeline_lace.py
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    processor: LaceProcessor | None = None,
) -> "LacePipeline":
    """Load a saved LACE pipeline.

    Args:
        pretrained_model_name_or_path: Local path or Hub id.
        processor: Optional processor override.

    Returns:
        Loaded pipeline with the serialized processor attached.
    """
    loaded_processor = (
        LaceProcessor.from_pretrained(pretrained_model_name_or_path)
        if processor is None
        else processor
    )
    pipe = super().from_pretrained(pretrained_model_name_or_path)
    pipe.processor = loaded_processor
    return pipe

normalize_condition_type

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

Normalize public condition aliases.

Parameters:

Name Type Description Default
condition_type ConditionType | str | None

Canonical condition enum, string alias, or None.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unsupported.

Examples:

>>> normalize_condition_type("cwh") is ConditionType.label_size
True
Source code in models/lace/src/lace/pipeline_lace.py
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
def normalize_condition_type(
    condition_type: ConditionType | str | None,
) -> ConditionType:
    """Normalize public condition aliases.

    Args:
        condition_type: Canonical condition enum, string alias, or ``None``.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unsupported.

    Examples:
        >>> normalize_condition_type("cwh") is ConditionType.label_size
        True
    """
    if isinstance(condition_type, ConditionType):
        canonical = condition_type
    elif condition_type is None:
        canonical = ConditionType.unconditional
    else:
        try:
            canonical = normalize_shared_condition_type(condition_type)
        except ValueError:
            key = condition_type.lower().replace("-", "_")
            try:
                canonical = _LACE_CONDITION_ALIASES[LaceConditionAlias(key)]
            except ValueError as exc:
                raise ValueError(
                    f"Unsupported LACE condition_type: {condition_type}"
                ) from exc

    if canonical not in _SUPPORTED_CONDITION_TYPES:
        raise ValueError(f"Unsupported LACE condition_type: {condition_type}")

    return canonical

normalize_output_type

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

Normalize public output type aliases.

Parameters:

Name Type Description Default
output_type PipelineOutputType | str

Output enum or string value.

required

Returns:

Type Description
PipelineOutputType

Canonical output type.

Raises:

Type Description
ValueError

If the output type is unsupported.

Source code in models/lace/src/lace/pipeline_lace.py
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def normalize_output_type(output_type: PipelineOutputType | str) -> PipelineOutputType:
    """Normalize public output type aliases.

    Args:
        output_type: Output enum or string value.

    Returns:
        Canonical output type.

    Raises:
        ValueError: If the output type is unsupported.
    """
    if isinstance(output_type, PipelineOutputType):
        return output_type
    try:
        return PipelineOutputType(output_type)
    except ValueError as exc:
        raise ValueError(f"Unsupported output_type: {output_type}") from exc

processing_lace

Processor for encoding and decoding LACE layout tensors.

LaceProcessor

Bases: ProcessorMixin

Encode public layout tensors into the continuous LACE sequence format.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset name serialized with the processor config.

required
labels list[str]

Ordered category labels without the padding label.

required
max_seq_length int

Maximum number of layout elements.

25

Examples:

>>> processor = LaceProcessor.from_dataset("publaynet")
>>> processor.seq_dim
10
Source code in models/lace/src/lace/processing_lace.py
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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
class LaceProcessor(ProcessorMixin):
    """Encode public layout tensors into the continuous LACE sequence format.

    Args:
        dataset: Canonical dataset name serialized with the processor config.
        labels: Ordered category labels without the padding label.
        max_seq_length: Maximum number of layout elements.

    Examples:
        >>> processor = LaceProcessor.from_dataset("publaynet")
        >>> processor.seq_dim
        10
    """

    config_name = "processor_config.json"

    def __init__(
        self,
        dataset: DatasetName | str,
        labels: list[str],
        max_seq_length: int = 25,
    ) -> None:
        """Initialize processor metadata.

        Args:
            dataset: Canonical dataset name.
            labels: Ordered category labels without padding.
            max_seq_length: Maximum number of layout elements.
        """
        super().__init__()
        self.dataset = str(normalize_dataset(dataset))
        self.labels = tuple(labels)
        self.max_seq_length = max_seq_length

    @classmethod
    def from_dataset(cls, dataset: DatasetName | str) -> "LaceProcessor":
        """Create a processor from built-in dataset metadata.

        Args:
            dataset: LACE dataset name or alias.

        Returns:
            Processor configured for the dataset.

        Raises:
            ValueError: If the dataset is unsupported.

        Examples:
            >>> LaceProcessor.from_dataset("rico13").pad_label_id
            13
        """
        spec = get_dataset_spec(dataset)
        return cls(
            dataset=str(spec.dataset),
            labels=[str(label) for label in spec.labels],
            max_seq_length=spec.max_seq_length,
        )

    @property
    def id2label(self) -> dict[int, str]:
        """Return the category id to label mapping."""
        return dict(enumerate(self.labels))

    @property
    def pad_label_id(self) -> int:
        """Return the padding label id."""
        return len(self.labels)

    @property
    def num_classes_with_pad(self) -> int:
        """Return the label-channel count including padding."""
        return len(self.labels) + 1

    @property
    def seq_dim(self) -> int:
        """Return the latent per-element feature size."""
        return self.num_classes_with_pad + 4

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

        Args:
            bbox: Boxes in ``box_format``.
            labels: Integer category labels.
            mask: Optional valid-element mask.
            box_format: Input box format.
            normalized: Whether input coordinates are already normalized.
            canvas_size: Pixel canvas size required when ``normalized`` is false.

        Returns:
            Dictionary containing encoded layout, normalized boxes, labels, and mask.

        Raises:
            ValueError: If pixel boxes are passed without ``canvas_size`` or if
                ``box_format`` is unsupported.

        Examples:
            >>> processor = LaceProcessor.from_dataset("publaynet")
            >>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
            >>> tuple(out["layout"].shape)
            (1, 25, 10)
        """
        bbox_t, labels_t, mask_t = prepare_layout_tensors(
            bbox=bbox,
            labels=labels,
            mask=mask,
            box_format=box_format,
            normalized=normalized,
            canvas_size=canvas_size,
        )
        bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t)
        return {
            LACE_LAYOUT_KEY: self.encode(bbox_t, labels_t, mask_t),
            LACE_BBOX_KEY: bbox_t,
            LACE_LABELS_KEY: labels_t,
            LACE_MASK_KEY: mask_t,
        }

    def pad(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
        max_seq_length: int | None = None,
    ) -> tuple[
        Float[torch.Tensor, "batch padded_elements 4"],
        Int[torch.Tensor, "batch padded_elements"],
        Bool[torch.Tensor, "batch padded_elements"],
    ]:
        """Pad a batch to ``max_seq_length``.

        Args:
            bbox: Normalized boxes with shape ``(batch, seq, 4)``.
            labels: Integer labels with shape ``(batch, seq)``.
            mask: Optional valid-element mask.
            max_seq_length: Optional override for output length.

        Returns:
            Padded boxes, labels, and mask.

        Raises:
            ValueError: If the input has too many elements.
        """
        max_len = max_seq_length or self.max_seq_length
        if bbox.shape[1] > max_len:
            raise ValueError(f"LACE supports at most {max_len} elements")

        if mask is None:
            mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
        pad_count = max_len - bbox.shape[1]
        if pad_count:
            bbox_pad = torch.zeros(
                bbox.shape[0], pad_count, 4, dtype=bbox.dtype, device=bbox.device
            )
            label_pad = torch.full(
                (labels.shape[0], pad_count),
                self.pad_label_id,
                dtype=labels.dtype,
                device=labels.device,
            )
            mask_pad = torch.zeros(
                mask.shape[0], pad_count, dtype=torch.bool, device=mask.device
            )
            bbox = torch.cat((bbox, bbox_pad), dim=1)
            labels = torch.cat((labels, label_pad), dim=1)
            mask = torch.cat((mask, mask_pad), dim=1)
        labels = labels.clone()
        labels[~mask] = self.pad_label_id
        return bbox, labels, mask

    def encode(
        self,
        bbox: Float[torch.Tensor, "batch elements 4"],
        labels: Int[torch.Tensor, "batch elements"],
        mask: Bool[torch.Tensor, "batch elements"] | None = None,
    ) -> Float[torch.Tensor, "batch padded_elements channels"]:
        """Encode normalized boxes and labels into the LACE latent range.

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

        Returns:
            Tensor with one-hot labels followed by box channels in ``[-1, 1]``.
        """
        bbox, labels, mask = self.pad(bbox, labels, mask)
        bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
        labels = labels.clamp(0, self.pad_label_id)
        labels[~mask] = self.pad_label_id
        one_hot = torch.nn.functional.one_hot(
            labels, num_classes=self.num_classes_with_pad
        ).to(dtype=bbox.dtype, device=bbox.device)
        return torch.cat((one_hot, bbox_in), dim=-1)

    def decode(
        self, layout: Float[torch.Tensor, "batch elements channels"], clamp: bool = True
    ) -> LayoutGenerationOutput:
        """Decode a LACE layout tensor into public output fields.

        Args:
            layout: Tensor with one-hot label channels and box channels.
            clamp: Whether to clamp latent box channels before conversion.

        Returns:
            Layout generation output with boxes in normalized center ``xywh``.
        """
        decoded = layout.clone()
        bbox_latent = decoded[:, :, self.num_classes_with_pad :]
        if clamp:
            bbox_latent = bbox_latent.clamp(-1.0, 1.0)
        bbox = bbox_latent / 2 + 0.5
        labels = decoded[:, :, : self.num_classes_with_pad].argmax(dim=2).long()
        mask = labels != self.pad_label_id
        return LayoutGenerationOutput(
            bbox=bbox,
            labels=labels,
            mask=mask,
            id2label=self.id2label,
            intermediates={"dataset": self.dataset},
        )

    def save_pretrained(  # ty: ignore[invalid-method-override]
        self, save_directory: str | Path
    ) -> None:
        """Save processor config to a Diffusers directory.

        Args:
            save_directory: Directory where ``processor_config.json`` is written.
        """
        super().save_pretrained(save_directory)

    @classmethod
    def from_pretrained(  # ty: ignore[invalid-method-override]
        cls,
        pretrained_model_name_or_path: str | Path,
        cache_dir: str | Path | None = None,
        force_download: bool = False,
        local_files_only: bool = False,
        token: str | bool | None = None,
        revision: str = "main",
    ) -> "LaceProcessor":
        """Load processor config from a Diffusers directory.

        Args:
            pretrained_model_name_or_path: Directory or Hub id containing
                ``processor_config.json``.
            cache_dir: Optional Hugging Face cache directory.
            force_download: Whether to force a fresh download.
            local_files_only: Whether to avoid network access.
            token: Optional Hugging Face token.
            revision: Hub revision to load.

        Returns:
            Loaded processor.
        """
        return super().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,
        )

id2label property

id2label: dict[int, str]

Return the category id to label mapping.

pad_label_id property

pad_label_id: int

Return the padding label id.

num_classes_with_pad property

num_classes_with_pad: int

Return the label-channel count including padding.

seq_dim property

seq_dim: int

Return the latent per-element feature size.

__init__

__init__(
    dataset: DatasetName | str,
    labels: list[str],
    max_seq_length: int = 25,
) -> None

Initialize processor metadata.

Parameters:

Name Type Description Default
dataset DatasetName | str

Canonical dataset name.

required
labels list[str]

Ordered category labels without padding.

required
max_seq_length int

Maximum number of layout elements.

25
Source code in models/lace/src/lace/processing_lace.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
def __init__(
    self,
    dataset: DatasetName | str,
    labels: list[str],
    max_seq_length: int = 25,
) -> None:
    """Initialize processor metadata.

    Args:
        dataset: Canonical dataset name.
        labels: Ordered category labels without padding.
        max_seq_length: Maximum number of layout elements.
    """
    super().__init__()
    self.dataset = str(normalize_dataset(dataset))
    self.labels = tuple(labels)
    self.max_seq_length = max_seq_length

from_dataset classmethod

from_dataset(dataset: DatasetName | str) -> 'LaceProcessor'

Create a processor from built-in dataset metadata.

Parameters:

Name Type Description Default
dataset DatasetName | str

LACE dataset name or alias.

required

Returns:

Type Description
'LaceProcessor'

Processor configured for the dataset.

Raises:

Type Description
ValueError

If the dataset is unsupported.

Examples:

>>> LaceProcessor.from_dataset("rico13").pad_label_id
13
Source code in models/lace/src/lace/processing_lace.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
@classmethod
def from_dataset(cls, dataset: DatasetName | str) -> "LaceProcessor":
    """Create a processor from built-in dataset metadata.

    Args:
        dataset: LACE dataset name or alias.

    Returns:
        Processor configured for the dataset.

    Raises:
        ValueError: If the dataset is unsupported.

    Examples:
        >>> LaceProcessor.from_dataset("rico13").pad_label_id
        13
    """
    spec = get_dataset_spec(dataset)
    return cls(
        dataset=str(spec.dataset),
        labels=[str(label) for label in spec.labels],
        max_seq_length=spec.max_seq_length,
    )

__call__

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

Encode a public layout batch.

Parameters:

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

Boxes in box_format.

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

Integer category labels.

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

Optional valid-element mask.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether input coordinates are already normalized.

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized is false.

None

Returns:

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

Dictionary containing encoded layout, normalized boxes, labels, and mask.

Raises:

Type Description
ValueError

If pixel boxes are passed without canvas_size or if box_format is unsupported.

Examples:

>>> processor = LaceProcessor.from_dataset("publaynet")
>>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
>>> tuple(out["layout"].shape)
(1, 25, 10)
Source code in models/lace/src/lace/processing_lace.py
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
def __call__(
    self,
    *,
    bbox: Float[torch.Tensor, "batch elements 4"]
    | Float[np.ndarray, "batch elements 4"]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "batch elements"]
    | Int[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "batch elements"]
    | Bool[np.ndarray, "batch elements"]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Encode a public layout batch.

    Args:
        bbox: Boxes in ``box_format``.
        labels: Integer category labels.
        mask: Optional valid-element mask.
        box_format: Input box format.
        normalized: Whether input coordinates are already normalized.
        canvas_size: Pixel canvas size required when ``normalized`` is false.

    Returns:
        Dictionary containing encoded layout, normalized boxes, labels, and mask.

    Raises:
        ValueError: If pixel boxes are passed without ``canvas_size`` or if
            ``box_format`` is unsupported.

    Examples:
        >>> processor = LaceProcessor.from_dataset("publaynet")
        >>> out = processor(bbox=[[[0.5, 0.5, 0.2, 0.2]]], labels=[[0]])
        >>> tuple(out["layout"].shape)
        (1, 25, 10)
    """
    bbox_t, labels_t, mask_t = prepare_layout_tensors(
        bbox=bbox,
        labels=labels,
        mask=mask,
        box_format=box_format,
        normalized=normalized,
        canvas_size=canvas_size,
    )
    bbox_t, labels_t, mask_t = self.pad(bbox_t, labels_t, mask_t)
    return {
        LACE_LAYOUT_KEY: self.encode(bbox_t, labels_t, mask_t),
        LACE_BBOX_KEY: bbox_t,
        LACE_LABELS_KEY: labels_t,
        LACE_MASK_KEY: mask_t,
    }

pad

pad(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
    max_seq_length: int | None = None,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]

Pad a batch to max_seq_length.

Parameters:

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

Normalized boxes with shape (batch, seq, 4).

required
labels Int[Tensor, 'batch elements']

Integer labels with shape (batch, seq).

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

Optional valid-element mask.

None
max_seq_length int | None

Optional override for output length.

None

Returns:

Type Description
tuple[Float[Tensor, 'batch padded_elements 4'], Int[Tensor, 'batch padded_elements'], Bool[Tensor, 'batch padded_elements']]

Padded boxes, labels, and mask.

Raises:

Type Description
ValueError

If the input has too many elements.

Source code in models/lace/src/lace/processing_lace.py
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
def pad(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
    max_seq_length: int | None = None,
) -> tuple[
    Float[torch.Tensor, "batch padded_elements 4"],
    Int[torch.Tensor, "batch padded_elements"],
    Bool[torch.Tensor, "batch padded_elements"],
]:
    """Pad a batch to ``max_seq_length``.

    Args:
        bbox: Normalized boxes with shape ``(batch, seq, 4)``.
        labels: Integer labels with shape ``(batch, seq)``.
        mask: Optional valid-element mask.
        max_seq_length: Optional override for output length.

    Returns:
        Padded boxes, labels, and mask.

    Raises:
        ValueError: If the input has too many elements.
    """
    max_len = max_seq_length or self.max_seq_length
    if bbox.shape[1] > max_len:
        raise ValueError(f"LACE supports at most {max_len} elements")

    if mask is None:
        mask = torch.ones(labels.shape, dtype=torch.bool, device=labels.device)
    pad_count = max_len - bbox.shape[1]
    if pad_count:
        bbox_pad = torch.zeros(
            bbox.shape[0], pad_count, 4, dtype=bbox.dtype, device=bbox.device
        )
        label_pad = torch.full(
            (labels.shape[0], pad_count),
            self.pad_label_id,
            dtype=labels.dtype,
            device=labels.device,
        )
        mask_pad = torch.zeros(
            mask.shape[0], pad_count, dtype=torch.bool, device=mask.device
        )
        bbox = torch.cat((bbox, bbox_pad), dim=1)
        labels = torch.cat((labels, label_pad), dim=1)
        mask = torch.cat((mask, mask_pad), dim=1)
    labels = labels.clone()
    labels[~mask] = self.pad_label_id
    return bbox, labels, mask

encode

encode(
    bbox: Float[Tensor, "batch elements 4"],
    labels: Int[Tensor, "batch elements"],
    mask: Bool[Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch padded_elements channels"]

Encode normalized boxes and labels into the LACE latent range.

Parameters:

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

Normalized center xywh boxes.

required
labels Int[Tensor, 'batch elements']

Integer labels.

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

Optional valid-element mask.

None

Returns:

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

Tensor with one-hot labels followed by box channels in [-1, 1].

Source code in models/lace/src/lace/processing_lace.py
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def encode(
    self,
    bbox: Float[torch.Tensor, "batch elements 4"],
    labels: Int[torch.Tensor, "batch elements"],
    mask: Bool[torch.Tensor, "batch elements"] | None = None,
) -> Float[torch.Tensor, "batch padded_elements channels"]:
    """Encode normalized boxes and labels into the LACE latent range.

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

    Returns:
        Tensor with one-hot labels followed by box channels in ``[-1, 1]``.
    """
    bbox, labels, mask = self.pad(bbox, labels, mask)
    bbox_in = 2 * (bbox.clamp(0.0, 1.0) - 0.5)
    labels = labels.clamp(0, self.pad_label_id)
    labels[~mask] = self.pad_label_id
    one_hot = torch.nn.functional.one_hot(
        labels, num_classes=self.num_classes_with_pad
    ).to(dtype=bbox.dtype, device=bbox.device)
    return torch.cat((one_hot, bbox_in), dim=-1)

decode

decode(
    layout: Float[Tensor, "batch elements channels"],
    clamp: bool = True,
) -> LayoutGenerationOutput

Decode a LACE layout tensor into public output fields.

Parameters:

Name Type Description Default
layout Float[Tensor, 'batch elements channels']

Tensor with one-hot label channels and box channels.

required
clamp bool

Whether to clamp latent box channels before conversion.

True

Returns:

Type Description
LayoutGenerationOutput

Layout generation output with boxes in normalized center xywh.

Source code in models/lace/src/lace/processing_lace.py
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
def decode(
    self, layout: Float[torch.Tensor, "batch elements channels"], clamp: bool = True
) -> LayoutGenerationOutput:
    """Decode a LACE layout tensor into public output fields.

    Args:
        layout: Tensor with one-hot label channels and box channels.
        clamp: Whether to clamp latent box channels before conversion.

    Returns:
        Layout generation output with boxes in normalized center ``xywh``.
    """
    decoded = layout.clone()
    bbox_latent = decoded[:, :, self.num_classes_with_pad :]
    if clamp:
        bbox_latent = bbox_latent.clamp(-1.0, 1.0)
    bbox = bbox_latent / 2 + 0.5
    labels = decoded[:, :, : self.num_classes_with_pad].argmax(dim=2).long()
    mask = labels != self.pad_label_id
    return LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=self.id2label,
        intermediates={"dataset": self.dataset},
    )

save_pretrained

save_pretrained(save_directory: str | Path) -> None

Save processor config to a Diffusers directory.

Parameters:

Name Type Description Default
save_directory str | Path

Directory where processor_config.json is written.

required
Source code in models/lace/src/lace/processing_lace.py
268
269
270
271
272
273
274
275
276
def save_pretrained(  # ty: ignore[invalid-method-override]
    self, save_directory: str | Path
) -> None:
    """Save processor config to a Diffusers directory.

    Args:
        save_directory: Directory where ``processor_config.json`` is written.
    """
    super().save_pretrained(save_directory)

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    cache_dir: str | Path | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
) -> "LaceProcessor"

Load processor config from a Diffusers directory.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Directory or Hub id containing processor_config.json.

required
cache_dir str | Path | None

Optional Hugging Face cache directory.

None
force_download bool

Whether to force a fresh download.

False
local_files_only bool

Whether to avoid network access.

False
token str | bool | None

Optional Hugging Face token.

None
revision str

Hub revision to load.

'main'

Returns:

Type Description
'LaceProcessor'

Loaded processor.

Source code in models/lace/src/lace/processing_lace.py
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
@classmethod
def from_pretrained(  # ty: ignore[invalid-method-override]
    cls,
    pretrained_model_name_or_path: str | Path,
    cache_dir: str | Path | None = None,
    force_download: bool = False,
    local_files_only: bool = False,
    token: str | bool | None = None,
    revision: str = "main",
) -> "LaceProcessor":
    """Load processor config from a Diffusers directory.

    Args:
        pretrained_model_name_or_path: Directory or Hub id containing
            ``processor_config.json``.
        cache_dir: Optional Hugging Face cache directory.
        force_download: Whether to force a fresh download.
        local_files_only: Whether to avoid network access.
        token: Optional Hugging Face token.
        revision: Hub revision to load.

    Returns:
        Loaded processor.
    """
    return super().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,
    )

scheduling_lace

DDIM-style scheduler utilities for LACE layout diffusion.

LaceSchedulerOutput dataclass

Bases: BaseOutput

Output returned by one reverse diffusion step.

Attributes:

Name Type Description
prev_sample Float[Tensor, 'batch elements channels']

Sample for the next denoising iteration.

pred_original_sample Float[Tensor, 'batch elements channels']

Scheduler estimate of the clean layout tensor.

Source code in models/lace/src/lace/scheduling_lace.py
22
23
24
25
26
27
28
29
30
31
32
@dataclass
class LaceSchedulerOutput(BaseOutput):
    """Output returned by one reverse diffusion step.

    Attributes:
        prev_sample: Sample for the next denoising iteration.
        pred_original_sample: Scheduler estimate of the clean layout tensor.
    """

    prev_sample: Float[torch.Tensor, "batch elements channels"]
    pred_original_sample: Float[torch.Tensor, "batch elements channels"]

LaceScheduler

Bases: SchedulerMixin, ConfigMixin

Scheduler for the converted LACE diffusion process.

Parameters:

Name Type Description Default
num_train_timesteps int

Number of timesteps used during training.

1000
beta_schedule BetaSchedule | str

Beta schedule enum or string value.

cosine
ddim_num_steps int

Default number of inference steps.

100
ddim_discretize DDIMDiscretization | str

DDIM discretization enum or string value.

uniform
eta float

Stochasticity parameter used by DDIM.

0.0
Source code in models/lace/src/lace/scheduling_lace.py
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
class LaceScheduler(SchedulerMixin, ConfigMixin):
    """Scheduler for the converted LACE diffusion process.

    Args:
        num_train_timesteps: Number of timesteps used during training.
        beta_schedule: Beta schedule enum or string value.
        ddim_num_steps: Default number of inference steps.
        ddim_discretize: DDIM discretization enum or string value.
        eta: Stochasticity parameter used by DDIM.
    """

    config_name = "scheduler_config.json"
    order = 1

    @register_to_config
    def __init__(
        self,
        *,
        num_train_timesteps: int = 1000,
        beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
        ddim_num_steps: int = 100,
        ddim_discretize: DDIMDiscretization | str = DDIMDiscretization.uniform,
        eta: float = 0.0,
    ) -> None:
        """Initialize scheduler state and default timesteps."""
        self.num_train_timesteps = num_train_timesteps
        canonical_beta = normalize_beta_schedule(beta_schedule)
        canonical_ddim = normalize_ddim_discretization(ddim_discretize)
        self.beta_schedule = str(canonical_beta)
        self.ddim_num_steps = ddim_num_steps
        self.ddim_discretize = str(canonical_ddim)
        self.eta = eta
        betas = make_beta_schedule(
            canonical_beta,
            num_timesteps=num_train_timesteps,
            start=0.0001,
            end=0.02,
        ).float()
        alphas = 1.0 - betas
        self.alphas_cumprod = alphas.cumprod(dim=0)
        self.timesteps = torch.empty(0, dtype=torch.long)
        self.set_timesteps(ddim_num_steps)

    def set_timesteps(
        self, num_inference_steps: int | None = None, device: torch.device | None = None
    ) -> None:
        """Set the inference timesteps.

        Args:
            num_inference_steps: Number of denoising steps. Uses the configured
                default when omitted.
            device: Optional device for scheduler tensors.
        """
        steps = num_inference_steps or self.ddim_num_steps
        ddim = make_ddim_timesteps(
            self.ddim_discretize, steps, self.num_train_timesteps
        )
        self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
        self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
        alphas_cumprod = self.alphas_cumprod.to(device)
        self.ddim_alphas = alphas_cumprod[self.ddim_timesteps]
        self.ddim_alphas_prev = torch.as_tensor(
            [alphas_cumprod[0].item()]
            + alphas_cumprod[self.ddim_timesteps[:-1]].tolist(),
            dtype=torch.float32,
            device=device,
        )
        self.ddim_sigmas = self.eta * torch.sqrt(
            (1 - self.ddim_alphas_prev)
            / (1 - self.ddim_alphas)
            * (1 - self.ddim_alphas / self.ddim_alphas_prev)
        )
        self.sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

    def add_noise(
        self,
        original_samples: Float[torch.Tensor, "batch elements channels"],
        noise: Float[torch.Tensor, "batch elements channels"],
        timesteps: Int[torch.Tensor, "batch"],
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Add forward-process noise to clean samples.

        Args:
            original_samples: Clean layout tensor.
            noise: Noise tensor with the same shape.
            timesteps: Per-sample timestep ids.

        Returns:
            Noisy samples at the requested timesteps.
        """
        alphas = self.alphas_cumprod.to(original_samples.device)
        sqrt_alpha = torch.gather(alphas.sqrt(), 0, timesteps).reshape(-1, 1, 1)
        sqrt_one_minus = torch.gather(torch.sqrt(1.0 - alphas), 0, timesteps).reshape(
            -1, 1, 1
        )
        return sqrt_alpha * original_samples + sqrt_one_minus * noise

    def initial_sample(
        self,
        batch_size: int,
        seq_len: int,
        seq_dim: int,
        *,
        device: torch.device,
        generator: torch.Generator | None = None,
        stochastic: bool = True,
    ) -> Float[torch.Tensor, "batch elements channels"]:
        """Create the initial denoising sample.

        Args:
            batch_size: Number of layouts.
            seq_len: Number of elements per layout.
            seq_dim: Number of channels per element.
            device: Device for the output tensor.
            generator: Optional torch generator.
            stochastic: Whether to sample noise or return zeros.

        Returns:
            Initial sample tensor.
        """
        if not stochastic:
            return torch.zeros(batch_size, seq_len, seq_dim, device=device)
        return torch.randn(
            batch_size, seq_len, seq_dim, device=device, generator=generator
        )

    def step(
        self,
        model_output: Float[torch.Tensor, "batch elements channels"],
        timestep: Int[torch.Tensor, "batch"],
        sample: Float[torch.Tensor, "batch elements channels"],
        index: int,
        generator: torch.Generator | None = None,
    ) -> LaceSchedulerOutput:
        """Take one reverse diffusion step.

        Args:
            model_output: Predicted noise from the denoiser.
            timestep: Public timestep tensor, kept for scheduler compatibility.
            sample: Current sample.
            index: Index into the scheduler timestep buffers.
            generator: Optional torch generator for stochastic DDIM noise.

        Returns:
            Previous sample and predicted clean sample.
        """
        del timestep
        alpha_t = self.ddim_alphas[index].to(sample.device)
        alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
        sigma_t = self.ddim_sigmas[index].to(sample.device)
        sqrt_one_minus = self.sqrt_one_minus_alphas[index].to(sample.device)
        pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
        direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
        noise = sigma_t * torch.randn(
            sample.shape,
            dtype=sample.dtype,
            device=sample.device,
            generator=generator,
        )
        prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
        return LaceSchedulerOutput(
            prev_sample=prev_sample, pred_original_sample=pred_original
        )

    def refinement_indices(self, max_timestep: int = 201) -> list[int]:
        """Return scheduler indices used by LACE refinement sampling.

        Args:
            max_timestep: Maximum one-indexed DDIM timestep included.

        Returns:
            Descending list of scheduler buffer indices.
        """
        total = int(torch.sum(self.ddim_timesteps <= max_timestep).item())
        return list(range(total - 1, -1, -1))

__init__

__init__(
    *,
    num_train_timesteps: int = 1000,
    beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
    ddim_num_steps: int = 100,
    ddim_discretize: DDIMDiscretization
    | str = DDIMDiscretization.uniform,
    eta: float = 0.0,
) -> None

Initialize scheduler state and default timesteps.

Source code in models/lace/src/lace/scheduling_lace.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
@register_to_config
def __init__(
    self,
    *,
    num_train_timesteps: int = 1000,
    beta_schedule: BetaSchedule | str = BetaSchedule.cosine,
    ddim_num_steps: int = 100,
    ddim_discretize: DDIMDiscretization | str = DDIMDiscretization.uniform,
    eta: float = 0.0,
) -> None:
    """Initialize scheduler state and default timesteps."""
    self.num_train_timesteps = num_train_timesteps
    canonical_beta = normalize_beta_schedule(beta_schedule)
    canonical_ddim = normalize_ddim_discretization(ddim_discretize)
    self.beta_schedule = str(canonical_beta)
    self.ddim_num_steps = ddim_num_steps
    self.ddim_discretize = str(canonical_ddim)
    self.eta = eta
    betas = make_beta_schedule(
        canonical_beta,
        num_timesteps=num_train_timesteps,
        start=0.0001,
        end=0.02,
    ).float()
    alphas = 1.0 - betas
    self.alphas_cumprod = alphas.cumprod(dim=0)
    self.timesteps = torch.empty(0, dtype=torch.long)
    self.set_timesteps(ddim_num_steps)

set_timesteps

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

Set the inference timesteps.

Parameters:

Name Type Description Default
num_inference_steps int | None

Number of denoising steps. Uses the configured default when omitted.

None
device device | None

Optional device for scheduler tensors.

None
Source code in models/lace/src/lace/scheduling_lace.py
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
def set_timesteps(
    self, num_inference_steps: int | None = None, device: torch.device | None = None
) -> None:
    """Set the inference timesteps.

    Args:
        num_inference_steps: Number of denoising steps. Uses the configured
            default when omitted.
        device: Optional device for scheduler tensors.
    """
    steps = num_inference_steps or self.ddim_num_steps
    ddim = make_ddim_timesteps(
        self.ddim_discretize, steps, self.num_train_timesteps
    )
    self.ddim_timesteps = torch.as_tensor(ddim, dtype=torch.long, device=device)
    self.timesteps = torch.flip(self.ddim_timesteps, dims=(0,))
    alphas_cumprod = self.alphas_cumprod.to(device)
    self.ddim_alphas = alphas_cumprod[self.ddim_timesteps]
    self.ddim_alphas_prev = torch.as_tensor(
        [alphas_cumprod[0].item()]
        + alphas_cumprod[self.ddim_timesteps[:-1]].tolist(),
        dtype=torch.float32,
        device=device,
    )
    self.ddim_sigmas = self.eta * torch.sqrt(
        (1 - self.ddim_alphas_prev)
        / (1 - self.ddim_alphas)
        * (1 - self.ddim_alphas / self.ddim_alphas_prev)
    )
    self.sqrt_one_minus_alphas = torch.sqrt(1.0 - self.ddim_alphas)

add_noise

add_noise(
    original_samples: Float[
        Tensor, "batch elements channels"
    ],
    noise: Float[Tensor, "batch elements channels"],
    timesteps: Int[Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]

Add forward-process noise to clean samples.

Parameters:

Name Type Description Default
original_samples Float[Tensor, 'batch elements channels']

Clean layout tensor.

required
noise Float[Tensor, 'batch elements channels']

Noise tensor with the same shape.

required
timesteps Int[Tensor, 'batch']

Per-sample timestep ids.

required

Returns:

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

Noisy samples at the requested timesteps.

Source code in models/lace/src/lace/scheduling_lace.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
def add_noise(
    self,
    original_samples: Float[torch.Tensor, "batch elements channels"],
    noise: Float[torch.Tensor, "batch elements channels"],
    timesteps: Int[torch.Tensor, "batch"],
) -> Float[torch.Tensor, "batch elements channels"]:
    """Add forward-process noise to clean samples.

    Args:
        original_samples: Clean layout tensor.
        noise: Noise tensor with the same shape.
        timesteps: Per-sample timestep ids.

    Returns:
        Noisy samples at the requested timesteps.
    """
    alphas = self.alphas_cumprod.to(original_samples.device)
    sqrt_alpha = torch.gather(alphas.sqrt(), 0, timesteps).reshape(-1, 1, 1)
    sqrt_one_minus = torch.gather(torch.sqrt(1.0 - alphas), 0, timesteps).reshape(
        -1, 1, 1
    )
    return sqrt_alpha * original_samples + sqrt_one_minus * noise

initial_sample

initial_sample(
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: device,
    generator: Generator | None = None,
    stochastic: bool = True,
) -> Float[torch.Tensor, "batch elements channels"]

Create the initial denoising sample.

Parameters:

Name Type Description Default
batch_size int

Number of layouts.

required
seq_len int

Number of elements per layout.

required
seq_dim int

Number of channels per element.

required
device device

Device for the output tensor.

required
generator Generator | None

Optional torch generator.

None
stochastic bool

Whether to sample noise or return zeros.

True

Returns:

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

Initial sample tensor.

Source code in models/lace/src/lace/scheduling_lace.py
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 initial_sample(
    self,
    batch_size: int,
    seq_len: int,
    seq_dim: int,
    *,
    device: torch.device,
    generator: torch.Generator | None = None,
    stochastic: bool = True,
) -> Float[torch.Tensor, "batch elements channels"]:
    """Create the initial denoising sample.

    Args:
        batch_size: Number of layouts.
        seq_len: Number of elements per layout.
        seq_dim: Number of channels per element.
        device: Device for the output tensor.
        generator: Optional torch generator.
        stochastic: Whether to sample noise or return zeros.

    Returns:
        Initial sample tensor.
    """
    if not stochastic:
        return torch.zeros(batch_size, seq_len, seq_dim, device=device)
    return torch.randn(
        batch_size, seq_len, seq_dim, device=device, generator=generator
    )

step

step(
    model_output: Float[Tensor, "batch elements channels"],
    timestep: Int[Tensor, "batch"],
    sample: Float[Tensor, "batch elements channels"],
    index: int,
    generator: Generator | None = None,
) -> LaceSchedulerOutput

Take one reverse diffusion step.

Parameters:

Name Type Description Default
model_output Float[Tensor, 'batch elements channels']

Predicted noise from the denoiser.

required
timestep Int[Tensor, 'batch']

Public timestep tensor, kept for scheduler compatibility.

required
sample Float[Tensor, 'batch elements channels']

Current sample.

required
index int

Index into the scheduler timestep buffers.

required
generator Generator | None

Optional torch generator for stochastic DDIM noise.

None

Returns:

Type Description
LaceSchedulerOutput

Previous sample and predicted clean sample.

Source code in models/lace/src/lace/scheduling_lace.py
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
def step(
    self,
    model_output: Float[torch.Tensor, "batch elements channels"],
    timestep: Int[torch.Tensor, "batch"],
    sample: Float[torch.Tensor, "batch elements channels"],
    index: int,
    generator: torch.Generator | None = None,
) -> LaceSchedulerOutput:
    """Take one reverse diffusion step.

    Args:
        model_output: Predicted noise from the denoiser.
        timestep: Public timestep tensor, kept for scheduler compatibility.
        sample: Current sample.
        index: Index into the scheduler timestep buffers.
        generator: Optional torch generator for stochastic DDIM noise.

    Returns:
        Previous sample and predicted clean sample.
    """
    del timestep
    alpha_t = self.ddim_alphas[index].to(sample.device)
    alpha_prev = self.ddim_alphas_prev[index].to(sample.device)
    sigma_t = self.ddim_sigmas[index].to(sample.device)
    sqrt_one_minus = self.sqrt_one_minus_alphas[index].to(sample.device)
    pred_original = (sample - sqrt_one_minus * model_output) / alpha_t.sqrt()
    direction = (1.0 - alpha_prev - sigma_t**2).sqrt() * model_output
    noise = sigma_t * torch.randn(
        sample.shape,
        dtype=sample.dtype,
        device=sample.device,
        generator=generator,
    )
    prev_sample = alpha_prev.sqrt() * pred_original + direction + noise
    return LaceSchedulerOutput(
        prev_sample=prev_sample, pred_original_sample=pred_original
    )

refinement_indices

refinement_indices(max_timestep: int = 201) -> list[int]

Return scheduler indices used by LACE refinement sampling.

Parameters:

Name Type Description Default
max_timestep int

Maximum one-indexed DDIM timestep included.

201

Returns:

Type Description
list[int]

Descending list of scheduler buffer indices.

Source code in models/lace/src/lace/scheduling_lace.py
199
200
201
202
203
204
205
206
207
208
209
def refinement_indices(self, max_timestep: int = 201) -> list[int]:
    """Return scheduler indices used by LACE refinement sampling.

    Args:
        max_timestep: Maximum one-indexed DDIM timestep included.

    Returns:
        Descending list of scheduler buffer indices.
    """
    total = int(torch.sum(self.ddim_timesteps <= max_timestep).item())
    return list(range(total - 1, -1, -1))