Skip to content

Laygen

Shared layout-generation utilities.

agents

Shared agent building blocks for text-conditioned layout generators.

Importing this module requires the optional laygen[agents] extra because provider execution is delegated to Pydantic AI. The rest of laygen remains usable without that extra.

BaseLayoutAgent

Bases: Generic[RawResponseT], ABC

Base Pydantic AI runner for text-conditioned layout agents.

Subclasses own model-specific exemplar selection, prompt serialization, and response parsing. This base class centralizes provider model resolution, Pydantic AI Agent construction, common public request validation, and shared output dictionary serialization.

Source code in lib/laygen/src/laygen/agents/core.py
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
class BaseLayoutAgent(Generic[RawResponseT], ABC):
    """Base Pydantic AI runner for text-conditioned layout agents.

    Subclasses own model-specific exemplar selection, prompt serialization, and
    response parsing. This base class centralizes provider model resolution,
    Pydantic AI ``Agent`` construction, common public request validation, and
    shared output dictionary serialization.
    """

    def __init__(
        self,
        *,
        model: ModelLike = None,
        model_env_var: str,
        raw_response_type: type[RawResponseT],
        instructions: str,
    ) -> None:
        """Initialize the provider runner.

        Args:
            model: Optional Pydantic AI model object or provider model id.
            model_env_var: Environment variable used when ``model`` is omitted.
            raw_response_type: Structured response model expected from the LLM.
            instructions: Provider instructions passed to Pydantic AI.
        """
        self.model_env_var = model_env_var
        self.raw_response_type = raw_response_type
        self.instructions = instructions
        self.agent = self.build_pydantic_agent(model=model)

    def resolve_model(self, model: ModelLike = None) -> ModelLike:
        """Resolve a per-call model override, constructor model, or env model id."""
        return model or os.getenv(self.model_env_var)

    def build_pydantic_agent(self, *, model: ModelLike = None) -> Agent[None]:
        """Build the underlying Pydantic AI agent."""
        return Agent(
            self.resolve_model(model),
            output_type=self.raw_response_type,
            instructions=self.instructions,
        )

    def run_raw_sync(
        self,
        model_prompt: str | Sequence[ChatMessageLike],
        *,
        model: ModelLike = None,
        model_settings: ModelSettings | None = None,
    ) -> RawResponseT:
        """Run the provider synchronously and return the structured raw response."""
        run_result = self.agent.run_sync(
            messages_to_text(model_prompt),
            model=model,
            model_settings=model_settings,
        )
        return cast(RawResponseT, run_result.output)

    def validate_generation_request(
        self,
        *,
        batch_size: int,
        condition_type: str | ConditionType,
        box_format: str | BoxFormat,
        canvas_size: tuple[int, int] | None,
        configured_canvas_size: int,
        supported_condition_types: tuple[
            ConditionType, ...
        ] = DEFAULT_SUPPORTED_CONDITIONS,
    ) -> tuple[ConditionType, BoxFormat]:
        """Validate shared generation arguments before provider execution."""
        normalized_condition_type = normalize_condition_type(condition_type)
        normalized_box_format = normalize_box_format(box_format)
        if batch_size != 1:
            msg = "provider-backed layout agents currently support batch_size=1."
            raise ValueError(msg)

        if normalized_condition_type not in supported_condition_types:
            msg = f"unsupported condition_type for this agent: {normalized_condition_type}"
            raise ValueError(msg)

        if canvas_size is not None and canvas_size != (
            configured_canvas_size,
            configured_canvas_size,
        ):
            msg = (
                "provider-backed layout agents use their configured square canvas_size."
            )
            raise ValueError(msg)

        return normalized_condition_type, normalized_box_format

    def output_to_dict(self, output: LayoutOutputLike) -> LayoutOutputDict:
        """Serialize the shared output with canonical layout schema keys."""
        return {
            "bbox": output.bbox,
            "labels": output.labels,
            "mask": output.mask,
            "id2label": output.id2label,
            "sequences": cast(
                LayoutAuxValue | None, getattr(output, "sequences", None)
            ),
            "scores": cast(LayoutAuxValue | None, getattr(output, "scores", None)),
            "trajectory": cast(
                LayoutAuxValue | None, getattr(output, "trajectory", None)
            ),
            "intermediates": cast(
                Mapping[str, LayoutAuxValue] | None,
                getattr(output, "intermediates", None),
            ),
        }

    def repair_response_text(self, text: str) -> str:
        """Hook for model-specific response repair before parsing."""
        return text

    def should_retry(self, exc: Exception, *, attempt: int) -> bool:
        """Hook for model-specific retry policy after provider or parse failure."""
        del exc, attempt
        return False

    def retry_delay_seconds(self, *, attempt: int) -> float:
        """Hook for retry backoff policies used by subclasses."""
        del attempt
        return 0.0

    def run_with_repair_policy(
        self,
        operation: Callable[[], RawResponseT],
        *,
        max_attempts: int = 1,
    ) -> RawResponseT:
        """Run an operation with subclass retry-policy hooks."""
        attempt = 1
        while True:
            try:
                return operation()
            except Exception as exc:
                if attempt >= max_attempts or not self.should_retry(
                    exc, attempt=attempt
                ):
                    raise

                attempt += 1

__init__

__init__(
    *,
    model: ModelLike = None,
    model_env_var: str,
    raw_response_type: type[RawResponseT],
    instructions: str,
) -> None

Initialize the provider runner.

Parameters:

Name Type Description Default
model ModelLike

Optional Pydantic AI model object or provider model id.

None
model_env_var str

Environment variable used when model is omitted.

required
raw_response_type type[RawResponseT]

Structured response model expected from the LLM.

required
instructions str

Provider instructions passed to Pydantic AI.

required
Source code in lib/laygen/src/laygen/agents/core.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def __init__(
    self,
    *,
    model: ModelLike = None,
    model_env_var: str,
    raw_response_type: type[RawResponseT],
    instructions: str,
) -> None:
    """Initialize the provider runner.

    Args:
        model: Optional Pydantic AI model object or provider model id.
        model_env_var: Environment variable used when ``model`` is omitted.
        raw_response_type: Structured response model expected from the LLM.
        instructions: Provider instructions passed to Pydantic AI.
    """
    self.model_env_var = model_env_var
    self.raw_response_type = raw_response_type
    self.instructions = instructions
    self.agent = self.build_pydantic_agent(model=model)

resolve_model

resolve_model(model: ModelLike = None) -> ModelLike

Resolve a per-call model override, constructor model, or env model id.

Source code in lib/laygen/src/laygen/agents/core.py
254
255
256
def resolve_model(self, model: ModelLike = None) -> ModelLike:
    """Resolve a per-call model override, constructor model, or env model id."""
    return model or os.getenv(self.model_env_var)

build_pydantic_agent

build_pydantic_agent(
    *, model: ModelLike = None
) -> Agent[None]

Build the underlying Pydantic AI agent.

Source code in lib/laygen/src/laygen/agents/core.py
258
259
260
261
262
263
264
def build_pydantic_agent(self, *, model: ModelLike = None) -> Agent[None]:
    """Build the underlying Pydantic AI agent."""
    return Agent(
        self.resolve_model(model),
        output_type=self.raw_response_type,
        instructions=self.instructions,
    )

run_raw_sync

run_raw_sync(
    model_prompt: str | Sequence[ChatMessageLike],
    *,
    model: ModelLike = None,
    model_settings: ModelSettings | None = None,
) -> RawResponseT

Run the provider synchronously and return the structured raw response.

Source code in lib/laygen/src/laygen/agents/core.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def run_raw_sync(
    self,
    model_prompt: str | Sequence[ChatMessageLike],
    *,
    model: ModelLike = None,
    model_settings: ModelSettings | None = None,
) -> RawResponseT:
    """Run the provider synchronously and return the structured raw response."""
    run_result = self.agent.run_sync(
        messages_to_text(model_prompt),
        model=model,
        model_settings=model_settings,
    )
    return cast(RawResponseT, run_result.output)

validate_generation_request

validate_generation_request(
    *,
    batch_size: int,
    condition_type: str | ConditionType,
    box_format: str | BoxFormat,
    canvas_size: tuple[int, int] | None,
    configured_canvas_size: int,
    supported_condition_types: tuple[
        ConditionType, ...
    ] = DEFAULT_SUPPORTED_CONDITIONS,
) -> tuple[ConditionType, BoxFormat]

Validate shared generation arguments before provider execution.

Source code in lib/laygen/src/laygen/agents/core.py
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
def validate_generation_request(
    self,
    *,
    batch_size: int,
    condition_type: str | ConditionType,
    box_format: str | BoxFormat,
    canvas_size: tuple[int, int] | None,
    configured_canvas_size: int,
    supported_condition_types: tuple[
        ConditionType, ...
    ] = DEFAULT_SUPPORTED_CONDITIONS,
) -> tuple[ConditionType, BoxFormat]:
    """Validate shared generation arguments before provider execution."""
    normalized_condition_type = normalize_condition_type(condition_type)
    normalized_box_format = normalize_box_format(box_format)
    if batch_size != 1:
        msg = "provider-backed layout agents currently support batch_size=1."
        raise ValueError(msg)

    if normalized_condition_type not in supported_condition_types:
        msg = f"unsupported condition_type for this agent: {normalized_condition_type}"
        raise ValueError(msg)

    if canvas_size is not None and canvas_size != (
        configured_canvas_size,
        configured_canvas_size,
    ):
        msg = (
            "provider-backed layout agents use their configured square canvas_size."
        )
        raise ValueError(msg)

    return normalized_condition_type, normalized_box_format

output_to_dict

output_to_dict(
    output: LayoutOutputLike,
) -> LayoutOutputDict

Serialize the shared output with canonical layout schema keys.

Source code in lib/laygen/src/laygen/agents/core.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def output_to_dict(self, output: LayoutOutputLike) -> LayoutOutputDict:
    """Serialize the shared output with canonical layout schema keys."""
    return {
        "bbox": output.bbox,
        "labels": output.labels,
        "mask": output.mask,
        "id2label": output.id2label,
        "sequences": cast(
            LayoutAuxValue | None, getattr(output, "sequences", None)
        ),
        "scores": cast(LayoutAuxValue | None, getattr(output, "scores", None)),
        "trajectory": cast(
            LayoutAuxValue | None, getattr(output, "trajectory", None)
        ),
        "intermediates": cast(
            Mapping[str, LayoutAuxValue] | None,
            getattr(output, "intermediates", None),
        ),
    }

repair_response_text

repair_response_text(text: str) -> str

Hook for model-specific response repair before parsing.

Source code in lib/laygen/src/laygen/agents/core.py
335
336
337
def repair_response_text(self, text: str) -> str:
    """Hook for model-specific response repair before parsing."""
    return text

should_retry

should_retry(exc: Exception, *, attempt: int) -> bool

Hook for model-specific retry policy after provider or parse failure.

Source code in lib/laygen/src/laygen/agents/core.py
339
340
341
342
def should_retry(self, exc: Exception, *, attempt: int) -> bool:
    """Hook for model-specific retry policy after provider or parse failure."""
    del exc, attempt
    return False

retry_delay_seconds

retry_delay_seconds(*, attempt: int) -> float

Hook for retry backoff policies used by subclasses.

Source code in lib/laygen/src/laygen/agents/core.py
344
345
346
347
def retry_delay_seconds(self, *, attempt: int) -> float:
    """Hook for retry backoff policies used by subclasses."""
    del attempt
    return 0.0

run_with_repair_policy

run_with_repair_policy(
    operation: Callable[[], RawResponseT],
    *,
    max_attempts: int = 1,
) -> RawResponseT

Run an operation with subclass retry-policy hooks.

Source code in lib/laygen/src/laygen/agents/core.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
def run_with_repair_policy(
    self,
    operation: Callable[[], RawResponseT],
    *,
    max_attempts: int = 1,
) -> RawResponseT:
    """Run an operation with subclass retry-policy hooks."""
    attempt = 1
    while True:
        try:
            return operation()
        except Exception as exc:
            if attempt >= max_attempts or not self.should_retry(
                exc, attempt=attempt
            ):
                raise

            attempt += 1

BaseExemplarSelector dataclass

Bases: Generic[ExampleT], ABC

Small base for selector strategies with shared candidate validation.

Source code in lib/laygen/src/laygen/agents/core.py
123
124
125
126
127
128
129
130
131
132
133
134
135
@dataclass
class BaseExemplarSelector(Generic[ExampleT], ABC):
    """Small base for selector strategies with shared candidate validation."""

    def validate_examples(self, examples: Sequence[ExampleT]) -> None:
        """Validate the selector has at least one candidate exemplar."""
        if not examples:
            msg = "exemplar selector requires at least one candidate"
            raise ValueError(msg)

    def selection_error(self, message: str) -> ValueError:
        """Build a consistent selector error."""
        return ValueError(f"exemplar selector: {message}")

validate_examples

validate_examples(examples: Sequence[ExampleT]) -> None

Validate the selector has at least one candidate exemplar.

Source code in lib/laygen/src/laygen/agents/core.py
127
128
129
130
131
def validate_examples(self, examples: Sequence[ExampleT]) -> None:
    """Validate the selector has at least one candidate exemplar."""
    if not examples:
        msg = "exemplar selector requires at least one candidate"
        raise ValueError(msg)

selection_error

selection_error(message: str) -> ValueError

Build a consistent selector error.

Source code in lib/laygen/src/laygen/agents/core.py
133
134
135
def selection_error(self, message: str) -> ValueError:
    """Build a consistent selector error."""
    return ValueError(f"exemplar selector: {message}")

BaseResponseParser dataclass

Bases: Generic[ParsedOutputT], ABC

Small base for parser strategies with shared repair/error hooks.

Source code in lib/laygen/src/laygen/agents/core.py
108
109
110
111
112
113
114
115
116
117
118
119
120
@dataclass
class BaseResponseParser(Generic[ParsedOutputT], ABC):
    """Small base for parser strategies with shared repair/error hooks."""

    parser_name: str = "response parser"

    def repair_response_text(self, text: str) -> str:
        """Repair provider text before parser-specific extraction."""
        return text

    def parser_error(self, message: str) -> RuntimeError:
        """Build a consistent parser error with parser context."""
        return RuntimeError(f"{self.parser_name}: {message}")

repair_response_text

repair_response_text(text: str) -> str

Repair provider text before parser-specific extraction.

Source code in lib/laygen/src/laygen/agents/core.py
114
115
116
def repair_response_text(self, text: str) -> str:
    """Repair provider text before parser-specific extraction."""
    return text

parser_error

parser_error(message: str) -> RuntimeError

Build a consistent parser error with parser context.

Source code in lib/laygen/src/laygen/agents/core.py
118
119
120
def parser_error(self, message: str) -> RuntimeError:
    """Build a consistent parser error with parser context."""
    return RuntimeError(f"{self.parser_name}: {message}")

ExemplarSelector

Bases: Protocol[ExampleT]

Strategy that chooses in-context examples for a layout prompt.

Source code in lib/laygen/src/laygen/agents/core.py
82
83
84
85
86
87
class ExemplarSelector(Protocol[ExampleT]):
    """Strategy that chooses in-context examples for a layout prompt."""

    def __call__(self, prompt: str, examples: Sequence[ExampleT]) -> Sequence[ExampleT]:
        """Return examples selected for ``prompt``."""
        ...

__call__

__call__(
    prompt: str, examples: Sequence[ExampleT]
) -> Sequence[ExampleT]

Return examples selected for prompt.

Source code in lib/laygen/src/laygen/agents/core.py
85
86
87
def __call__(self, prompt: str, examples: Sequence[ExampleT]) -> Sequence[ExampleT]:
    """Return examples selected for ``prompt``."""
    ...

LayoutItem2DLike

Bases: Protocol

Minimal parsed 2D item required by the shared output builder.

Source code in lib/laygen/src/laygen/agents/core.py
138
139
140
141
142
143
144
145
146
147
148
149
class LayoutItem2DLike(Protocol):
    """Minimal parsed 2D item required by the shared output builder."""

    @property
    def label(self) -> str:
        """Display label parsed for this item."""
        ...

    @property
    def bbox_xywh(self) -> tuple[float, float, float, float]:
        """Normalized center ``xywh`` box."""
        ...

label property

label: str

Display label parsed for this item.

bbox_xywh property

bbox_xywh: tuple[float, float, float, float]

Normalized center xywh box.

LayoutOutputDict

Bases: TypedDict

Dictionary form of the canonical layout generation schema.

Source code in lib/laygen/src/laygen/agents/core.py
67
68
69
70
71
72
73
74
75
76
77
78
79
class LayoutOutputDict(TypedDict):
    """Dictionary form of the canonical layout generation schema."""

    bbox: (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    )
    labels: Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]
    id2label: dict[int, str]
    sequences: LayoutAuxValue | None
    scores: LayoutAuxValue | None
    trajectory: LayoutAuxValue | None
    intermediates: Mapping[str, LayoutAuxValue] | None

PromptBuilder

Bases: Protocol[ExampleT]

Strategy that serializes a user request and exemplars for a provider.

Source code in lib/laygen/src/laygen/agents/core.py
90
91
92
93
94
95
96
97
class PromptBuilder(Protocol[ExampleT]):
    """Strategy that serializes a user request and exemplars for a provider."""

    def __call__(
        self, prompt: str, exemplars: Sequence[ExampleT]
    ) -> str | Sequence[ChatMessageLike]:
        """Serialize ``prompt`` and ``exemplars`` for model execution."""
        ...

__call__

__call__(
    prompt: str, exemplars: Sequence[ExampleT]
) -> str | Sequence[ChatMessageLike]

Serialize prompt and exemplars for model execution.

Source code in lib/laygen/src/laygen/agents/core.py
93
94
95
96
97
def __call__(
    self, prompt: str, exemplars: Sequence[ExampleT]
) -> str | Sequence[ChatMessageLike]:
    """Serialize ``prompt`` and ``exemplars`` for model execution."""
    ...

ResponseParser

Bases: Protocol

Strategy that converts provider text into a shared layout output.

Source code in lib/laygen/src/laygen/agents/core.py
100
101
102
103
104
105
class ResponseParser(Protocol):
    """Strategy that converts provider text into a shared layout output."""

    def __call__(self, text: str, *, canvas_size: int) -> LayoutGenerationOutput:
        """Parse provider ``text`` into the shared output schema."""
        ...

__call__

__call__(
    text: str, *, canvas_size: int
) -> LayoutGenerationOutput

Parse provider text into the shared output schema.

Source code in lib/laygen/src/laygen/agents/core.py
103
104
105
def __call__(self, text: str, *, canvas_size: int) -> LayoutGenerationOutput:
    """Parse provider ``text`` into the shared output schema."""
    ...

layout_items_to_output

layout_items_to_output(
    items: Sequence[LayoutItem2DLike],
    *,
    id2label: Mapping[int, str],
    intermediates: Mapping[str, LayoutAuxValue]
    | None = None,
) -> LayoutGenerationOutput

Build the torch-backed shared normalized center-xywh output schema.

Source code in lib/laygen/src/laygen/agents/core.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def layout_items_to_output(
    items: Sequence[LayoutItem2DLike],
    *,
    id2label: Mapping[int, str],
    intermediates: Mapping[str, LayoutAuxValue] | None = None,
) -> LayoutGenerationOutput:
    """Build the torch-backed shared normalized center-``xywh`` output schema."""
    import torch

    from laygen.modeling_outputs import LayoutGenerationOutput

    label2id = {label: idx for idx, label in id2label.items()}
    bbox_values = [item.bbox_xywh for item in items]
    label_values = [label2id[item.label] for item in items]
    bbox = torch.tensor([bbox_values], dtype=torch.float32)
    labels = torch.tensor([label_values], dtype=torch.long)
    mask = torch.ones((1, len(items)), dtype=torch.bool)
    return LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=dict(id2label),
        intermediates=intermediates,
    )

messages_to_text

messages_to_text(
    messages: str | Sequence[ChatMessageLike],
) -> str

Convert provider chat messages to deterministic plain text.

Pydantic AI accepts both provider-native strings and structured chat-like messages. The shared base class sends one plain string to keep downstream behavior independent of provider-specific chat transport details.

Source code in lib/laygen/src/laygen/agents/core.py
184
185
186
187
188
189
190
191
192
193
194
195
def messages_to_text(messages: str | Sequence[ChatMessageLike]) -> str:
    """Convert provider chat messages to deterministic plain text.

    Pydantic AI accepts both provider-native strings and structured chat-like
    messages. The shared base class sends one plain string to keep downstream
    behavior independent of provider-specific chat transport details.
    """
    if isinstance(messages, str):
        return messages
    return "\n\n".join(
        f"{message['role'].upper()}:\n{message['content']}" for message in messages
    )

core

Provider-independent base classes for layout-generation agents.

ChatMessageLike

Bases: TypedDict

Chat-style message with role and content text fields.

Source code in lib/laygen/src/laygen/agents/core.py
52
53
54
55
56
class ChatMessageLike(TypedDict):
    """Chat-style message with role and content text fields."""

    role: str
    content: str

LayoutOutputDict

Bases: TypedDict

Dictionary form of the canonical layout generation schema.

Source code in lib/laygen/src/laygen/agents/core.py
67
68
69
70
71
72
73
74
75
76
77
78
79
class LayoutOutputDict(TypedDict):
    """Dictionary form of the canonical layout generation schema."""

    bbox: (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    )
    labels: Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]
    id2label: dict[int, str]
    sequences: LayoutAuxValue | None
    scores: LayoutAuxValue | None
    trajectory: LayoutAuxValue | None
    intermediates: Mapping[str, LayoutAuxValue] | None

ExemplarSelector

Bases: Protocol[ExampleT]

Strategy that chooses in-context examples for a layout prompt.

Source code in lib/laygen/src/laygen/agents/core.py
82
83
84
85
86
87
class ExemplarSelector(Protocol[ExampleT]):
    """Strategy that chooses in-context examples for a layout prompt."""

    def __call__(self, prompt: str, examples: Sequence[ExampleT]) -> Sequence[ExampleT]:
        """Return examples selected for ``prompt``."""
        ...
__call__
__call__(
    prompt: str, examples: Sequence[ExampleT]
) -> Sequence[ExampleT]

Return examples selected for prompt.

Source code in lib/laygen/src/laygen/agents/core.py
85
86
87
def __call__(self, prompt: str, examples: Sequence[ExampleT]) -> Sequence[ExampleT]:
    """Return examples selected for ``prompt``."""
    ...

PromptBuilder

Bases: Protocol[ExampleT]

Strategy that serializes a user request and exemplars for a provider.

Source code in lib/laygen/src/laygen/agents/core.py
90
91
92
93
94
95
96
97
class PromptBuilder(Protocol[ExampleT]):
    """Strategy that serializes a user request and exemplars for a provider."""

    def __call__(
        self, prompt: str, exemplars: Sequence[ExampleT]
    ) -> str | Sequence[ChatMessageLike]:
        """Serialize ``prompt`` and ``exemplars`` for model execution."""
        ...
__call__
__call__(
    prompt: str, exemplars: Sequence[ExampleT]
) -> str | Sequence[ChatMessageLike]

Serialize prompt and exemplars for model execution.

Source code in lib/laygen/src/laygen/agents/core.py
93
94
95
96
97
def __call__(
    self, prompt: str, exemplars: Sequence[ExampleT]
) -> str | Sequence[ChatMessageLike]:
    """Serialize ``prompt`` and ``exemplars`` for model execution."""
    ...

ResponseParser

Bases: Protocol

Strategy that converts provider text into a shared layout output.

Source code in lib/laygen/src/laygen/agents/core.py
100
101
102
103
104
105
class ResponseParser(Protocol):
    """Strategy that converts provider text into a shared layout output."""

    def __call__(self, text: str, *, canvas_size: int) -> LayoutGenerationOutput:
        """Parse provider ``text`` into the shared output schema."""
        ...
__call__
__call__(
    text: str, *, canvas_size: int
) -> LayoutGenerationOutput

Parse provider text into the shared output schema.

Source code in lib/laygen/src/laygen/agents/core.py
103
104
105
def __call__(self, text: str, *, canvas_size: int) -> LayoutGenerationOutput:
    """Parse provider ``text`` into the shared output schema."""
    ...

BaseResponseParser dataclass

Bases: Generic[ParsedOutputT], ABC

Small base for parser strategies with shared repair/error hooks.

Source code in lib/laygen/src/laygen/agents/core.py
108
109
110
111
112
113
114
115
116
117
118
119
120
@dataclass
class BaseResponseParser(Generic[ParsedOutputT], ABC):
    """Small base for parser strategies with shared repair/error hooks."""

    parser_name: str = "response parser"

    def repair_response_text(self, text: str) -> str:
        """Repair provider text before parser-specific extraction."""
        return text

    def parser_error(self, message: str) -> RuntimeError:
        """Build a consistent parser error with parser context."""
        return RuntimeError(f"{self.parser_name}: {message}")
repair_response_text
repair_response_text(text: str) -> str

Repair provider text before parser-specific extraction.

Source code in lib/laygen/src/laygen/agents/core.py
114
115
116
def repair_response_text(self, text: str) -> str:
    """Repair provider text before parser-specific extraction."""
    return text
parser_error
parser_error(message: str) -> RuntimeError

Build a consistent parser error with parser context.

Source code in lib/laygen/src/laygen/agents/core.py
118
119
120
def parser_error(self, message: str) -> RuntimeError:
    """Build a consistent parser error with parser context."""
    return RuntimeError(f"{self.parser_name}: {message}")

BaseExemplarSelector dataclass

Bases: Generic[ExampleT], ABC

Small base for selector strategies with shared candidate validation.

Source code in lib/laygen/src/laygen/agents/core.py
123
124
125
126
127
128
129
130
131
132
133
134
135
@dataclass
class BaseExemplarSelector(Generic[ExampleT], ABC):
    """Small base for selector strategies with shared candidate validation."""

    def validate_examples(self, examples: Sequence[ExampleT]) -> None:
        """Validate the selector has at least one candidate exemplar."""
        if not examples:
            msg = "exemplar selector requires at least one candidate"
            raise ValueError(msg)

    def selection_error(self, message: str) -> ValueError:
        """Build a consistent selector error."""
        return ValueError(f"exemplar selector: {message}")
validate_examples
validate_examples(examples: Sequence[ExampleT]) -> None

Validate the selector has at least one candidate exemplar.

Source code in lib/laygen/src/laygen/agents/core.py
127
128
129
130
131
def validate_examples(self, examples: Sequence[ExampleT]) -> None:
    """Validate the selector has at least one candidate exemplar."""
    if not examples:
        msg = "exemplar selector requires at least one candidate"
        raise ValueError(msg)
selection_error
selection_error(message: str) -> ValueError

Build a consistent selector error.

Source code in lib/laygen/src/laygen/agents/core.py
133
134
135
def selection_error(self, message: str) -> ValueError:
    """Build a consistent selector error."""
    return ValueError(f"exemplar selector: {message}")

LayoutItem2DLike

Bases: Protocol

Minimal parsed 2D item required by the shared output builder.

Source code in lib/laygen/src/laygen/agents/core.py
138
139
140
141
142
143
144
145
146
147
148
149
class LayoutItem2DLike(Protocol):
    """Minimal parsed 2D item required by the shared output builder."""

    @property
    def label(self) -> str:
        """Display label parsed for this item."""
        ...

    @property
    def bbox_xywh(self) -> tuple[float, float, float, float]:
        """Normalized center ``xywh`` box."""
        ...
label property
label: str

Display label parsed for this item.

bbox_xywh property
bbox_xywh: tuple[float, float, float, float]

Normalized center xywh box.

LayoutOutputLike

Bases: Protocol

Minimal shared layout output fields used for dict serialization.

Source code in lib/laygen/src/laygen/agents/core.py
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
class LayoutOutputLike(Protocol):
    """Minimal shared layout output fields used for dict serialization."""

    @property
    def bbox(
        self,
    ) -> (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    ):
        """Layout boxes."""
        ...

    @property
    def labels(
        self,
    ) -> Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]:
        """Layout labels."""
        ...

    @property
    def mask(
        self,
    ) -> Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]:
        """Valid element mask."""
        ...

    @property
    def id2label(self) -> dict[int, str]:
        """Dataset-local label names."""
        ...
bbox property
bbox: (
    Float[ndarray, "batch elements 4"]
    | Float[Tensor, "batch elements 4"]
)

Layout boxes.

labels property
labels: (
    Int[ndarray, "batch elements"]
    | Int[Tensor, "batch elements"]
)

Layout labels.

mask property
mask: (
    Bool[ndarray, "batch elements"]
    | Bool[Tensor, "batch elements"]
)

Valid element mask.

id2label property
id2label: dict[int, str]

Dataset-local label names.

BaseLayoutAgent

Bases: Generic[RawResponseT], ABC

Base Pydantic AI runner for text-conditioned layout agents.

Subclasses own model-specific exemplar selection, prompt serialization, and response parsing. This base class centralizes provider model resolution, Pydantic AI Agent construction, common public request validation, and shared output dictionary serialization.

Source code in lib/laygen/src/laygen/agents/core.py
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
class BaseLayoutAgent(Generic[RawResponseT], ABC):
    """Base Pydantic AI runner for text-conditioned layout agents.

    Subclasses own model-specific exemplar selection, prompt serialization, and
    response parsing. This base class centralizes provider model resolution,
    Pydantic AI ``Agent`` construction, common public request validation, and
    shared output dictionary serialization.
    """

    def __init__(
        self,
        *,
        model: ModelLike = None,
        model_env_var: str,
        raw_response_type: type[RawResponseT],
        instructions: str,
    ) -> None:
        """Initialize the provider runner.

        Args:
            model: Optional Pydantic AI model object or provider model id.
            model_env_var: Environment variable used when ``model`` is omitted.
            raw_response_type: Structured response model expected from the LLM.
            instructions: Provider instructions passed to Pydantic AI.
        """
        self.model_env_var = model_env_var
        self.raw_response_type = raw_response_type
        self.instructions = instructions
        self.agent = self.build_pydantic_agent(model=model)

    def resolve_model(self, model: ModelLike = None) -> ModelLike:
        """Resolve a per-call model override, constructor model, or env model id."""
        return model or os.getenv(self.model_env_var)

    def build_pydantic_agent(self, *, model: ModelLike = None) -> Agent[None]:
        """Build the underlying Pydantic AI agent."""
        return Agent(
            self.resolve_model(model),
            output_type=self.raw_response_type,
            instructions=self.instructions,
        )

    def run_raw_sync(
        self,
        model_prompt: str | Sequence[ChatMessageLike],
        *,
        model: ModelLike = None,
        model_settings: ModelSettings | None = None,
    ) -> RawResponseT:
        """Run the provider synchronously and return the structured raw response."""
        run_result = self.agent.run_sync(
            messages_to_text(model_prompt),
            model=model,
            model_settings=model_settings,
        )
        return cast(RawResponseT, run_result.output)

    def validate_generation_request(
        self,
        *,
        batch_size: int,
        condition_type: str | ConditionType,
        box_format: str | BoxFormat,
        canvas_size: tuple[int, int] | None,
        configured_canvas_size: int,
        supported_condition_types: tuple[
            ConditionType, ...
        ] = DEFAULT_SUPPORTED_CONDITIONS,
    ) -> tuple[ConditionType, BoxFormat]:
        """Validate shared generation arguments before provider execution."""
        normalized_condition_type = normalize_condition_type(condition_type)
        normalized_box_format = normalize_box_format(box_format)
        if batch_size != 1:
            msg = "provider-backed layout agents currently support batch_size=1."
            raise ValueError(msg)

        if normalized_condition_type not in supported_condition_types:
            msg = f"unsupported condition_type for this agent: {normalized_condition_type}"
            raise ValueError(msg)

        if canvas_size is not None and canvas_size != (
            configured_canvas_size,
            configured_canvas_size,
        ):
            msg = (
                "provider-backed layout agents use their configured square canvas_size."
            )
            raise ValueError(msg)

        return normalized_condition_type, normalized_box_format

    def output_to_dict(self, output: LayoutOutputLike) -> LayoutOutputDict:
        """Serialize the shared output with canonical layout schema keys."""
        return {
            "bbox": output.bbox,
            "labels": output.labels,
            "mask": output.mask,
            "id2label": output.id2label,
            "sequences": cast(
                LayoutAuxValue | None, getattr(output, "sequences", None)
            ),
            "scores": cast(LayoutAuxValue | None, getattr(output, "scores", None)),
            "trajectory": cast(
                LayoutAuxValue | None, getattr(output, "trajectory", None)
            ),
            "intermediates": cast(
                Mapping[str, LayoutAuxValue] | None,
                getattr(output, "intermediates", None),
            ),
        }

    def repair_response_text(self, text: str) -> str:
        """Hook for model-specific response repair before parsing."""
        return text

    def should_retry(self, exc: Exception, *, attempt: int) -> bool:
        """Hook for model-specific retry policy after provider or parse failure."""
        del exc, attempt
        return False

    def retry_delay_seconds(self, *, attempt: int) -> float:
        """Hook for retry backoff policies used by subclasses."""
        del attempt
        return 0.0

    def run_with_repair_policy(
        self,
        operation: Callable[[], RawResponseT],
        *,
        max_attempts: int = 1,
    ) -> RawResponseT:
        """Run an operation with subclass retry-policy hooks."""
        attempt = 1
        while True:
            try:
                return operation()
            except Exception as exc:
                if attempt >= max_attempts or not self.should_retry(
                    exc, attempt=attempt
                ):
                    raise

                attempt += 1
__init__
__init__(
    *,
    model: ModelLike = None,
    model_env_var: str,
    raw_response_type: type[RawResponseT],
    instructions: str,
) -> None

Initialize the provider runner.

Parameters:

Name Type Description Default
model ModelLike

Optional Pydantic AI model object or provider model id.

None
model_env_var str

Environment variable used when model is omitted.

required
raw_response_type type[RawResponseT]

Structured response model expected from the LLM.

required
instructions str

Provider instructions passed to Pydantic AI.

required
Source code in lib/laygen/src/laygen/agents/core.py
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
def __init__(
    self,
    *,
    model: ModelLike = None,
    model_env_var: str,
    raw_response_type: type[RawResponseT],
    instructions: str,
) -> None:
    """Initialize the provider runner.

    Args:
        model: Optional Pydantic AI model object or provider model id.
        model_env_var: Environment variable used when ``model`` is omitted.
        raw_response_type: Structured response model expected from the LLM.
        instructions: Provider instructions passed to Pydantic AI.
    """
    self.model_env_var = model_env_var
    self.raw_response_type = raw_response_type
    self.instructions = instructions
    self.agent = self.build_pydantic_agent(model=model)
resolve_model
resolve_model(model: ModelLike = None) -> ModelLike

Resolve a per-call model override, constructor model, or env model id.

Source code in lib/laygen/src/laygen/agents/core.py
254
255
256
def resolve_model(self, model: ModelLike = None) -> ModelLike:
    """Resolve a per-call model override, constructor model, or env model id."""
    return model or os.getenv(self.model_env_var)
build_pydantic_agent
build_pydantic_agent(
    *, model: ModelLike = None
) -> Agent[None]

Build the underlying Pydantic AI agent.

Source code in lib/laygen/src/laygen/agents/core.py
258
259
260
261
262
263
264
def build_pydantic_agent(self, *, model: ModelLike = None) -> Agent[None]:
    """Build the underlying Pydantic AI agent."""
    return Agent(
        self.resolve_model(model),
        output_type=self.raw_response_type,
        instructions=self.instructions,
    )
run_raw_sync
run_raw_sync(
    model_prompt: str | Sequence[ChatMessageLike],
    *,
    model: ModelLike = None,
    model_settings: ModelSettings | None = None,
) -> RawResponseT

Run the provider synchronously and return the structured raw response.

Source code in lib/laygen/src/laygen/agents/core.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
def run_raw_sync(
    self,
    model_prompt: str | Sequence[ChatMessageLike],
    *,
    model: ModelLike = None,
    model_settings: ModelSettings | None = None,
) -> RawResponseT:
    """Run the provider synchronously and return the structured raw response."""
    run_result = self.agent.run_sync(
        messages_to_text(model_prompt),
        model=model,
        model_settings=model_settings,
    )
    return cast(RawResponseT, run_result.output)
validate_generation_request
validate_generation_request(
    *,
    batch_size: int,
    condition_type: str | ConditionType,
    box_format: str | BoxFormat,
    canvas_size: tuple[int, int] | None,
    configured_canvas_size: int,
    supported_condition_types: tuple[
        ConditionType, ...
    ] = DEFAULT_SUPPORTED_CONDITIONS,
) -> tuple[ConditionType, BoxFormat]

Validate shared generation arguments before provider execution.

Source code in lib/laygen/src/laygen/agents/core.py
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
def validate_generation_request(
    self,
    *,
    batch_size: int,
    condition_type: str | ConditionType,
    box_format: str | BoxFormat,
    canvas_size: tuple[int, int] | None,
    configured_canvas_size: int,
    supported_condition_types: tuple[
        ConditionType, ...
    ] = DEFAULT_SUPPORTED_CONDITIONS,
) -> tuple[ConditionType, BoxFormat]:
    """Validate shared generation arguments before provider execution."""
    normalized_condition_type = normalize_condition_type(condition_type)
    normalized_box_format = normalize_box_format(box_format)
    if batch_size != 1:
        msg = "provider-backed layout agents currently support batch_size=1."
        raise ValueError(msg)

    if normalized_condition_type not in supported_condition_types:
        msg = f"unsupported condition_type for this agent: {normalized_condition_type}"
        raise ValueError(msg)

    if canvas_size is not None and canvas_size != (
        configured_canvas_size,
        configured_canvas_size,
    ):
        msg = (
            "provider-backed layout agents use their configured square canvas_size."
        )
        raise ValueError(msg)

    return normalized_condition_type, normalized_box_format
output_to_dict
output_to_dict(
    output: LayoutOutputLike,
) -> LayoutOutputDict

Serialize the shared output with canonical layout schema keys.

Source code in lib/laygen/src/laygen/agents/core.py
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
def output_to_dict(self, output: LayoutOutputLike) -> LayoutOutputDict:
    """Serialize the shared output with canonical layout schema keys."""
    return {
        "bbox": output.bbox,
        "labels": output.labels,
        "mask": output.mask,
        "id2label": output.id2label,
        "sequences": cast(
            LayoutAuxValue | None, getattr(output, "sequences", None)
        ),
        "scores": cast(LayoutAuxValue | None, getattr(output, "scores", None)),
        "trajectory": cast(
            LayoutAuxValue | None, getattr(output, "trajectory", None)
        ),
        "intermediates": cast(
            Mapping[str, LayoutAuxValue] | None,
            getattr(output, "intermediates", None),
        ),
    }
repair_response_text
repair_response_text(text: str) -> str

Hook for model-specific response repair before parsing.

Source code in lib/laygen/src/laygen/agents/core.py
335
336
337
def repair_response_text(self, text: str) -> str:
    """Hook for model-specific response repair before parsing."""
    return text
should_retry
should_retry(exc: Exception, *, attempt: int) -> bool

Hook for model-specific retry policy after provider or parse failure.

Source code in lib/laygen/src/laygen/agents/core.py
339
340
341
342
def should_retry(self, exc: Exception, *, attempt: int) -> bool:
    """Hook for model-specific retry policy after provider or parse failure."""
    del exc, attempt
    return False
retry_delay_seconds
retry_delay_seconds(*, attempt: int) -> float

Hook for retry backoff policies used by subclasses.

Source code in lib/laygen/src/laygen/agents/core.py
344
345
346
347
def retry_delay_seconds(self, *, attempt: int) -> float:
    """Hook for retry backoff policies used by subclasses."""
    del attempt
    return 0.0
run_with_repair_policy
run_with_repair_policy(
    operation: Callable[[], RawResponseT],
    *,
    max_attempts: int = 1,
) -> RawResponseT

Run an operation with subclass retry-policy hooks.

Source code in lib/laygen/src/laygen/agents/core.py
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
def run_with_repair_policy(
    self,
    operation: Callable[[], RawResponseT],
    *,
    max_attempts: int = 1,
) -> RawResponseT:
    """Run an operation with subclass retry-policy hooks."""
    attempt = 1
    while True:
        try:
            return operation()
        except Exception as exc:
            if attempt >= max_attempts or not self.should_retry(
                exc, attempt=attempt
            ):
                raise

            attempt += 1

messages_to_text

messages_to_text(
    messages: str | Sequence[ChatMessageLike],
) -> str

Convert provider chat messages to deterministic plain text.

Pydantic AI accepts both provider-native strings and structured chat-like messages. The shared base class sends one plain string to keep downstream behavior independent of provider-specific chat transport details.

Source code in lib/laygen/src/laygen/agents/core.py
184
185
186
187
188
189
190
191
192
193
194
195
def messages_to_text(messages: str | Sequence[ChatMessageLike]) -> str:
    """Convert provider chat messages to deterministic plain text.

    Pydantic AI accepts both provider-native strings and structured chat-like
    messages. The shared base class sends one plain string to keep downstream
    behavior independent of provider-specific chat transport details.
    """
    if isinstance(messages, str):
        return messages
    return "\n\n".join(
        f"{message['role'].upper()}:\n{message['content']}" for message in messages
    )

layout_items_to_output

layout_items_to_output(
    items: Sequence[LayoutItem2DLike],
    *,
    id2label: Mapping[int, str],
    intermediates: Mapping[str, LayoutAuxValue]
    | None = None,
) -> LayoutGenerationOutput

Build the torch-backed shared normalized center-xywh output schema.

Source code in lib/laygen/src/laygen/agents/core.py
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
def layout_items_to_output(
    items: Sequence[LayoutItem2DLike],
    *,
    id2label: Mapping[int, str],
    intermediates: Mapping[str, LayoutAuxValue] | None = None,
) -> LayoutGenerationOutput:
    """Build the torch-backed shared normalized center-``xywh`` output schema."""
    import torch

    from laygen.modeling_outputs import LayoutGenerationOutput

    label2id = {label: idx for idx, label in id2label.items()}
    bbox_values = [item.bbox_xywh for item in items]
    label_values = [label2id[item.label] for item in items]
    bbox = torch.tensor([bbox_values], dtype=torch.float32)
    labels = torch.tensor([label_values], dtype=torch.long)
    mask = torch.ones((1, len(items)), dtype=torch.bool)
    return LayoutGenerationOutput(
        bbox=bbox,
        labels=labels,
        mask=mask,
        id2label=dict(id2label),
        intermediates=intermediates,
    )

testing

Testing helpers for provider-backed layout agents.

function_model_from_text

function_model_from_text(text: str) -> FunctionModel

Build a deterministic FunctionModel returning one text response.

Source code in lib/laygen/src/laygen/agents/testing.py
21
22
23
24
25
26
27
def function_model_from_text(text: str) -> FunctionModel:
    """Build a deterministic ``FunctionModel`` returning one text response."""

    def respond(_messages: Sequence[ModelMessage], _info: AgentInfo) -> ModelResponse:
        return ModelResponse(parts=[TextPart(content=text)])

    return FunctionModel(respond)

test_model_from_text

test_model_from_text(text: str) -> TestModel

Build a deterministic TestModel returning one custom text response.

Source code in lib/laygen/src/laygen/agents/testing.py
30
31
32
def test_model_from_text(text: str) -> TestModel:
    """Build a deterministic ``TestModel`` returning one custom text response."""
    return TestModel(custom_output_text=text)

assert_agent_output_schema

assert_agent_output_schema(
    run_agent: Callable[[], LayoutGenerationOutput],
    *,
    batch_size: int = 1,
) -> LayoutGenerationOutput

Run an agent callable and assert the shared output schema.

Source code in lib/laygen/src/laygen/agents/testing.py
35
36
37
38
39
40
41
42
43
def assert_agent_output_schema(
    run_agent: Callable[[], LayoutGenerationOutput],
    *,
    batch_size: int = 1,
) -> LayoutGenerationOutput:
    """Run an agent callable and assert the shared output schema."""
    output = run_agent()
    assert_layout_output_schema(output, batch_size=batch_size)
    return output

common

Shared public APIs for layout-generation packages.

BoxFormat

Bases: StrEnum

Supported bounding-box coordinate formats.

Source code in lib/laygen/src/laygen/common/bbox.py
21
22
23
24
25
26
class BoxFormat(StrEnum):
    """Supported bounding-box coordinate formats."""

    xywh = auto()
    ltwh = auto()
    ltrb = auto()

ConditionAlias

Bases: StrEnum

Supported public and release-specific condition aliases.

Source code in lib/laygen/src/laygen/common/conditions.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class ConditionAlias(StrEnum):
    """Supported public and release-specific condition aliases."""

    unconditional = auto()
    uncond = auto()
    ugen = auto()
    random_generate = auto()
    label = auto()
    c = auto()
    cat_cond = auto()
    category_generate = auto()
    gen_t = auto()
    label_size = auto()
    cwh = auto()
    chw = auto()
    size_cond = auto()
    gen_ts = auto()
    completion = auto()
    partial = auto()
    complete = auto()
    elem_compl = auto()
    completion_generate = auto()
    refinement = auto()
    refine = auto()
    text = auto()
    prompt = auto()
    text_to_layout = auto()
    content_image = auto()
    content = auto()
    image = auto()
    visual = auto()
    relation = auto()
    scene_graph = auto()
    graph = auto()
    gen_r = auto()
    hierarchical = auto()
    hierarchy = auto()
    coarse_to_fine = auto()
    retrieval = auto()
    retrieved = auto()
    retrieval_examples = 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()

SamplingMode

Bases: StrEnum

Supported categorical sampling modes.

Source code in lib/laygen/src/laygen/common/discrete.py
21
22
23
24
25
26
27
28
29
class SamplingMode(StrEnum):
    """Supported categorical sampling modes."""

    deterministic = auto()
    random = auto()
    gumbel = auto()
    top_k = auto()
    top_p = auto()
    top_k_top_p = auto()

DatasetName

Bases: StrEnum

Canonical dataset names supported by the shared label registry.

Source code in lib/laygen/src/laygen/common/labels.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DatasetName(StrEnum):
    """Canonical dataset names supported by the shared label registry."""

    rico25 = auto()
    rico13 = auto()
    publaynet = auto()
    magazine = auto()
    nsr_1k = "nsr-1k"
    grit = auto()
    coco = auto()
    vg_msdn = "vg-msdn"
    coco_grounded = "coco-grounded"
    web = auto()
    webui = auto()
    housegan_floorplan_vectorized = "housegan-floorplan-vectorized"

ParityMetric dataclass

Reference-parity metric row included in generated model cards.

Attributes:

Name Type Description
dataset str

Dataset or checkpoint name.

tokenizer_exact str

Exact-match ratio for tokenizer round-trips.

deterministic_exact str

Exact-match ratio for deterministic samples.

logits_max_abs float

Maximum absolute denoiser-logit difference.

logits_max_rel float

Maximum relative denoiser-logit difference.

Source code in lib/laygen/src/laygen/common/model_card.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@dataclass(frozen=True)
class ParityMetric:
    """Reference-parity metric row included in generated model cards.

    Attributes:
        dataset: Dataset or checkpoint name.
        tokenizer_exact: Exact-match ratio for tokenizer round-trips.
        deterministic_exact: Exact-match ratio for deterministic samples.
        logits_max_abs: Maximum absolute denoiser-logit difference.
        logits_max_rel: Maximum relative denoiser-logit difference.
    """

    dataset: str
    tokenizer_exact: str
    deterministic_exact: str
    logits_max_abs: float
    logits_max_rel: float

WhitespaceTokenizerMixin

Mixin for tokenizers backed by tokenizer-local id dictionaries.

Source code in lib/laygen/src/laygen/common/tokenization.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
class WhitespaceTokenizerMixin:
    """Mixin for tokenizers backed by tokenizer-local id dictionaries."""

    _token2id: dict[str, int]
    _id2token: dict[int, str]
    unk_token_id: int
    unk_token: str

    @property
    def vocab_size(self) -> int:
        """Return the number of vocabulary entries."""
        return len(self._token2id)

    def get_vocab(self) -> dict[str, int]:
        """Return token-to-id mapping."""
        return dict(self._token2id)

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

    def _convert_token_to_id(self, token: str) -> int:
        return convert_token_to_id(self._token2id, token, self.unk_token_id)

    def _convert_id_to_token(self, index: int) -> str:
        return convert_id_to_token(self._id2token, index, self.unk_token)

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join layout tokens with spaces."""
        return join_tokens(tokens)

vocab_size property

vocab_size: int

Return the number of vocabulary entries.

get_vocab

get_vocab() -> dict[str, int]

Return token-to-id mapping.

Source code in lib/laygen/src/laygen/common/tokenization.py
86
87
88
def get_vocab(self) -> dict[str, int]:
    """Return token-to-id mapping."""
    return dict(self._token2id)

convert_tokens_to_string

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

Join layout tokens with spaces.

Source code in lib/laygen/src/laygen/common/tokenization.py
102
103
104
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join layout tokens with spaces."""
    return join_tokens(tokens)

normalize_box_format

normalize_box_format(
    box_format: BoxFormat | str,
) -> BoxFormat

Convert a public box-format value to BoxFormat.

Parameters:

Name Type Description Default
box_format BoxFormat | str

Box format enum or its string value.

required

Returns:

Type Description
BoxFormat

Normalized BoxFormat enum.

Raises:

Type Description
ValueError

If box_format is not supported.

Source code in lib/laygen/src/laygen/common/bbox.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def normalize_box_format(box_format: BoxFormat | str) -> BoxFormat:
    """Convert a public box-format value to ``BoxFormat``.

    Args:
        box_format: Box format enum or its string value.

    Returns:
        Normalized ``BoxFormat`` enum.

    Raises:
        ValueError: If ``box_format`` is not supported.
    """
    if isinstance(box_format, BoxFormat):
        return box_format
    try:
        return BoxFormat(box_format)
    except ValueError as exc:
        raise ValueError(f"Unsupported box_format: {box_format}") from exc

normalize_condition_type

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

Normalize condition aliases to a canonical ConditionType.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition enum or a public/release alias.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unknown.

Examples:

>>> str(normalize_condition_type("gen_t"))
'label'
>>> str(normalize_condition_type("gen_r"))
'relation'
Source code in lib/laygen/src/laygen/common/conditions.py
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
def normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize condition aliases to a canonical ``ConditionType``.

    Args:
        condition_type: Canonical condition enum or a public/release alias.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unknown.

    Examples:
        >>> str(normalize_condition_type("gen_t"))
        'label'
        >>> str(normalize_condition_type("gen_r"))
        'relation'
    """
    if isinstance(condition_type, ConditionType):
        return condition_type
    try:
        return _CONDITION_ALIASES[
            ConditionAlias(condition_type.lower().replace("-", "_"))
        ]
    except ValueError as exc:
        raise ValueError(f"Unknown condition_type: {condition_type}") from exc

normalize_sampling_mode

normalize_sampling_mode(
    sampling: SamplingMode | str,
) -> SamplingMode

Convert a public sampling value to SamplingMode.

Parameters:

Name Type Description Default
sampling SamplingMode | str

Sampling enum or its string value.

required

Returns:

Type Description
SamplingMode

Normalized SamplingMode enum.

Raises:

Type Description
ValueError

If sampling is not supported.

Source code in lib/laygen/src/laygen/common/discrete.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def normalize_sampling_mode(sampling: SamplingMode | str) -> SamplingMode:
    """Convert a public sampling value to ``SamplingMode``.

    Args:
        sampling: Sampling enum or its string value.

    Returns:
        Normalized ``SamplingMode`` enum.

    Raises:
        ValueError: If ``sampling`` is not supported.
    """
    if isinstance(sampling, SamplingMode):
        return sampling
    try:
        return SamplingMode(sampling)
    except ValueError as exc:
        raise ValueError(f"Unsupported sampling mode: {sampling}") from exc

normalize_enum_value

normalize_enum_value(
    value: EnumT | str,
    enum_type: type[EnumT],
    *,
    option_name: str,
) -> EnumT

Normalize a public string-or-enum option to a StrEnum value.

Source code in lib/laygen/src/laygen/common/enums.py
11
12
13
14
15
16
17
18
19
20
21
22
23
def normalize_enum_value(
    value: EnumT | str,
    enum_type: type[EnumT],
    *,
    option_name: str,
) -> EnumT:
    """Normalize a public string-or-enum option to a ``StrEnum`` value."""
    if isinstance(value, enum_type):
        return value
    try:
        return enum_type(value)
    except ValueError as exc:
        raise ValueError(f"Unsupported {option_name}: {value}") from exc

max_elements_for_dataset

max_elements_for_dataset(
    dataset_name: DatasetName | str,
) -> int

Return the shared maximum element count for a dataset.

Source code in lib/laygen/src/laygen/common/labels.py
202
203
204
def max_elements_for_dataset(dataset_name: DatasetName | str) -> int:
    """Return the shared maximum element count for a dataset."""
    return DATASET_METADATA[normalize_dataset_name(dataset_name)]["max_elements"]

normalize_dataset_name

normalize_dataset_name(
    dataset_name: DatasetName | str,
) -> DatasetName

Normalize common dataset aliases to canonical registry names.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

User-facing dataset name or release alias.

required

Returns:

Type Description
DatasetName

Canonical dataset name used by the shared label registry.

Raises:

Type Description
ValueError

If the dataset name is unknown.

Examples:

>>> str(normalize_dataset_name("rico25_max25"))
'rico25'
Source code in lib/laygen/src/laygen/common/labels.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def normalize_dataset_name(dataset_name: DatasetName | str) -> DatasetName:
    """Normalize common dataset aliases to canonical registry names.

    Args:
        dataset_name: User-facing dataset name or release alias.

    Returns:
        Canonical dataset name used by the shared label registry.

    Raises:
        ValueError: If the dataset name is unknown.

    Examples:
        >>> str(normalize_dataset_name("rico25_max25"))
        'rico25'
    """
    if isinstance(dataset_name, DatasetName):
        return dataset_name
    key = dataset_name.lower().replace("-", "_")
    try:
        return _ALIASES[key]
    except KeyError as exc:
        raise ValueError(f"Unknown dataset_name: {dataset_name}") from exc

build_layout_model_card

build_layout_model_card(
    *,
    model_id: str,
    model_name: str,
    dataset_ids: Sequence[str],
    license: str,
    library_name: str,
    pipeline_tag: str,
    tags: Sequence[str],
    model_details: str,
    intended_uses: str,
    limitations: str,
    how_to_use: str,
    training_data: str,
    parity_metrics: Sequence[ParityMetricInput],
    citation_bibtex: str,
    original_implementation_url: str,
    model_summary: str | None = None,
    developers: str | None = None,
    model_type: str = "Layout generation model.",
    base_model: str | None = None,
    paper: str | None = None,
    preprocessing: str | None = None,
    training_regime: str | None = None,
    testing_data: str | None = None,
    testing_metrics: str | None = None,
    results_summary: str | None = None,
    model_specs: str | None = None,
    compute_infrastructure: str | None = None,
    hardware_requirements: str | None = None,
    software: str | None = None,
    citation_apa: str | None = None,
) -> ModelCard

Build a Hugging Face model card for a layout-generation checkpoint.

Parameters:

Name Type Description Default
model_id str

Hub model id displayed in the card title.

required
model_name str

Human-readable model name.

required
dataset_ids Sequence[str]

Hub dataset ids used by the checkpoint.

required
license str

SPDX-style license id for YAML metadata.

required
library_name str

Hub library name, such as diffusers.

required
pipeline_tag str

Hub task tag.

required
tags Sequence[str]

Additional Hub tags.

required
model_details str

User-facing model description.

required
intended_uses str

Direct-use description.

required
limitations str

Known limitations and risks.

required
how_to_use str

Python snippet without surrounding fences.

required
training_data str

Training-data description.

required
parity_metrics Sequence[ParityMetricInput]

Parity table rows.

required
citation_bibtex str

BibTeX citation without surrounding fences.

required
original_implementation_url str

URL for the upstream implementation.

required
model_summary str | None

Short model summary for the card header.

None
developers str | None

Original developer attribution.

None
model_type str

User-facing model family/type.

'Layout generation model.'
base_model str | None

Base-model or conversion-relationship statement.

None
paper str | None

Paper URL.

None
preprocessing str | None

Preprocessing description.

None
training_regime str | None

Training-regime description.

None
testing_data str | None

Evaluation data or fixture description.

None
testing_metrics str | None

Evaluation metric description.

None
results_summary str | None

Summary of recorded parity results.

None
model_specs str | None

Architecture and objective summary.

None
compute_infrastructure str | None

Conversion/parity compute description.

None
hardware_requirements str | None

Runtime and parity hardware requirements.

None
software str | None

Runtime and parity software requirements.

None
citation_apa str | None

Optional APA-style citation.

None

Returns:

Type Description
ModelCard

Rendered huggingface_hub.ModelCard instance with structured

ModelCard

metadata attached.

Examples:

>>> card = layoutdm_model_card(dataset="rico25")
>>> card.data.to_dict()["library_name"]
'diffusers'
Source code in lib/laygen/src/laygen/common/model_card.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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
def build_layout_model_card(
    *,
    model_id: str,
    model_name: str,
    dataset_ids: Sequence[str],
    license: str,
    library_name: str,
    pipeline_tag: str,
    tags: Sequence[str],
    model_details: str,
    intended_uses: str,
    limitations: str,
    how_to_use: str,
    training_data: str,
    parity_metrics: Sequence[ParityMetricInput],
    citation_bibtex: str,
    original_implementation_url: str,
    model_summary: str | None = None,
    developers: str | None = None,
    model_type: str = "Layout generation model.",
    base_model: str | None = None,
    paper: str | None = None,
    preprocessing: str | None = None,
    training_regime: str | None = None,
    testing_data: str | None = None,
    testing_metrics: str | None = None,
    results_summary: str | None = None,
    model_specs: str | None = None,
    compute_infrastructure: str | None = None,
    hardware_requirements: str | None = None,
    software: str | None = None,
    citation_apa: str | None = None,
) -> ModelCard:
    """Build a Hugging Face model card for a layout-generation checkpoint.

    Args:
        model_id: Hub model id displayed in the card title.
        model_name: Human-readable model name.
        dataset_ids: Hub dataset ids used by the checkpoint.
        license: SPDX-style license id for YAML metadata.
        library_name: Hub library name, such as ``diffusers``.
        pipeline_tag: Hub task tag.
        tags: Additional Hub tags.
        model_details: User-facing model description.
        intended_uses: Direct-use description.
        limitations: Known limitations and risks.
        how_to_use: Python snippet without surrounding fences.
        training_data: Training-data description.
        parity_metrics: Parity table rows.
        citation_bibtex: BibTeX citation without surrounding fences.
        original_implementation_url: URL for the upstream implementation.
        model_summary: Short model summary for the card header.
        developers: Original developer attribution.
        model_type: User-facing model family/type.
        base_model: Base-model or conversion-relationship statement.
        paper: Paper URL.
        preprocessing: Preprocessing description.
        training_regime: Training-regime description.
        testing_data: Evaluation data or fixture description.
        testing_metrics: Evaluation metric description.
        results_summary: Summary of recorded parity results.
        model_specs: Architecture and objective summary.
        compute_infrastructure: Conversion/parity compute description.
        hardware_requirements: Runtime and parity hardware requirements.
        software: Runtime and parity software requirements.
        citation_apa: Optional APA-style citation.

    Returns:
        Rendered ``huggingface_hub.ModelCard`` instance with structured
        metadata attached.

    Examples:
        >>> card = layoutdm_model_card(dataset="rico25")
        >>> card.data.to_dict()["library_name"]
        'diffusers'
    """
    metadata = _model_card_metadata(
        model_name=model_name,
        license=license,
        library_name=library_name,
        pipeline_tag=pipeline_tag,
        tags=tags,
        dataset_ids=dataset_ids,
    )
    card_data = ModelCardData(**metadata)
    parity_table = _parity_table(parity_metrics)
    card = ModelCard.from_template(
        card_data,
        model_id=model_id,
        model_summary=(
            model_summary
            or f"{model_name} is a converted checkpoint for layout generation."
        ),
        model_description=model_details,
        developers=developers or "See the original implementation and citation.",
        funded_by=(
            "Funding for the original checkpoint is not separately reported in "
            "this converted artifact."
        ),
        shared_by="creative-graphic-design",
        model_type=model_type,
        language=(
            "The model does not process natural language inputs; metadata uses "
            "English for this model card and category label names."
        ),
        license=license,
        base_model=(
            base_model
            or "Not applicable. This is a conversion of the original checkpoint, "
            "not a fine-tuned derivative of a Hub model."
        ),
        repo=original_implementation_url,
        paper=paper or "See the citation and original implementation.",
        demo="No hosted demo is packaged with this checkpoint.",
        direct_use=intended_uses,
        downstream_use=(
            "Use the generated normalized boxes, labels, and masks as layout "
            "priors for design tooling, document analysis research, or "
            "controlled rendering pipelines that perform their own validation."
        ),
        out_of_scope_use=(
            "Do not use this checkpoint as an OCR model, image renderer, "
            "semantic document understanding model, accessibility verifier, or "
            "unreviewed production UI generator. The model predicts layout "
            "structure only and can produce implausible or overlapping boxes."
        ),
        bias_risks_limitations=limitations,
        bias_recommendations=(
            "Inspect generated layouts before downstream use, validate boxes "
            "against application constraints, and evaluate separately for each "
            "target dataset or design domain."
        ),
        get_started_code=f"```python\n{how_to_use.strip()}\n```",
        training_data=training_data,
        preprocessing=(
            preprocessing
            or "Package adapters convert upstream layout representations to "
            "normalized center `xywh` boxes, dataset-local labels, and mask-based "
            "padding at the public API boundary."
        ),
        training_regime=(
            training_regime
            or "Original upstream training regime; this package converts released "
            "artifacts and does not retrain them."
        ),
        speeds_sizes_times=(
            "Training speed, elapsed time, and hardware are not included in "
            "the upstream checkpoint bundle used for conversion."
        ),
        testing_data=(
            testing_data
            or "Reference parity tests use local outputs from the original "
            "implementation for each converted dataset or checkpoint."
        ),
        testing_factors=(
            "Parity is checked separately for each dataset conversion so that "
            "dataset-specific tokenization and checkpoint weights are covered."
        ),
        testing_metrics=(
            testing_metrics
            or "Recorded parity metrics report exact-match counts or numeric "
            "maximum absolute and relative errors against the original "
            "implementation."
        ),
        results=parity_table,
        results_summary=(
            results_summary
            or "Recorded parity results are listed in the table above; see the "
            "package README for commands that regenerate local references."
        ),
        model_examination=(
            "No separate interpretability study is packaged with this converted "
            "checkpoint."
        ),
        hardware_type=(
            "Original training hardware is not reported in this converted "
            "artifact. Reference regeneration is documented for "
            "`CUDA_VISIBLE_DEVICES=0` when a CUDA device is available."
        ),
        hours_used=(
            "Original training hours are not reported in this converted artifact."
        ),
        cloud_provider=(
            "Original training cloud provider is not reported in this converted "
            "artifact."
        ),
        cloud_region=(
            "Original training compute region is not reported in this converted "
            "artifact."
        ),
        co2_emitted=(
            "Carbon emissions cannot be estimated from the released checkpoint "
            "bundle alone."
        ),
        model_specs=(
            model_specs
            or "The converted package exposes the model, preprocessing, and "
            "pipeline or agent components needed to reproduce local inference."
        ),
        compute_infrastructure=(
            compute_infrastructure
            or "Conversion and parity generation run locally through the `uv` "
            "workspace commands documented in the package README."
        ),
        hardware_requirements=(
            hardware_requirements
            or "CPU is sufficient for package loading and lightweight smoke tests. "
            "CUDA may be required for heavyweight reference parity depending on "
            "the original implementation."
        ),
        software=(
            software
            or "Python 3.11+, the package workspace dependencies, and any optional "
            "original-code dependencies documented by the package."
        ),
        citation_bibtex=f"```bibtex\n{citation_bibtex.strip()}\n```",
        citation_apa=citation_apa or "See the BibTeX citation above.",
        glossary=(
            "`xywh` means normalized center-x, center-y, width, and height. "
            "`Tokenizer exact` counts matching encoded and decoded token "
            "positions. `Logits max abs` and `logits max rel` are maximum "
            "differences against the original denoiser outputs."
        ),
        more_information=(
            "See the package README for copy-paste reproduction commands, "
            "checkpoint conversion, and reference fixture generation."
        ),
        model_card_authors="creative-graphic-design maintainers.",
        model_card_contact=(
            "Open an issue or pull request in the creative-graphic-design "
            "design-generators repository."
        ),
    )
    return card

layoutdm_model_card

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

Build the LayoutDM model card for a converted checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LayoutDM dataset name, either "rico25" or "publaynet".

required
parity_metrics Sequence[ParityMetricInput] | None

Optional parity rows. Defaults to the checked conversion metrics used by this package.

None

Returns:

Type Description
ModelCard

Validated model card for the requested LayoutDM checkpoint.

Raises:

Type Description
ValueError

If dataset is unsupported.

Examples:

>>> card = layoutdm_model_card(dataset="publaynet")
>>> card.data.to_dict()["datasets"]
['creative-graphic-design/PubLayNet']
Source code in lib/laygen/src/laygen/common/model_card.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def layoutdm_model_card(
    *,
    dataset: DatasetName | str,
    parity_metrics: Sequence[ParityMetricInput] | None = None,
) -> ModelCard:
    """Build the LayoutDM model card for a converted checkpoint.

    Args:
        dataset: LayoutDM dataset name, either ``"rico25"`` or ``"publaynet"``.
        parity_metrics: Optional parity rows. Defaults to the checked conversion
            metrics used by this package.

    Returns:
        Validated model card for the requested LayoutDM checkpoint.

    Raises:
        ValueError: If ``dataset`` is unsupported.

    Examples:
        >>> card = layoutdm_model_card(dataset="publaynet")
        >>> card.data.to_dict()["datasets"]
        ['creative-graphic-design/PubLayNet']
    """
    dataset_id = _layoutdm_dataset_id(dataset)
    dataset_config = _layoutdm_dataset_config(dataset)
    model_id = f"creative-graphic-design/layoutdm-{dataset}"
    model_name = f"LayoutDM {dataset}"
    metrics = parity_metrics or [
        ParityMetric(
            dataset=dataset,
            tokenizer_exact="125/125",
            deterministic_exact="125/125",
            logits_max_abs=0.0,
            logits_max_rel=0.0,
        )
    ]
    how_to_use = f"""
from layout_dm import LayoutDMPipeline

path = ".cache/layout-dm/converted/layoutdm-{dataset}"
# After Hub publication: from_pretrained("{model_id}")
pipe = LayoutDMPipeline.from_pretrained(path)
out = pipe(batch_size=1, seed=0, sampling="deterministic")
print(out.bbox, out.labels, out.mask)
"""
    return build_layout_model_card(
        model_id=model_id,
        model_name=model_name,
        dataset_ids=[dataset_id],
        license="apache-2.0",
        library_name="diffusers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "layout-dm",
            "diffusers",
            dataset,
        ],
        model_details=(
            "Diffusers-format conversion of the LayoutDM checkpoint for "
            f"`{dataset}`. The pipeline generates normalized center `xywh` layout "
            "boxes, category labels, and masks."
        ),
        intended_uses=(
            "Use this checkpoint for research and evaluation of document and UI "
            "layout generation workflows."
        ),
        limitations=(
            "The converted checkpoint follows the original LayoutDM release and is "
            "intended for layout synthesis, not for image rendering or OCR."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{dataset_id}`"
            f"{dataset_config} as released by "
            "the original LayoutDM project."
        ),
        parity_metrics=metrics,
        citation_bibtex=_LAYOUTDM_BIBTEX,
        original_implementation_url=("https://github.com/CyberAgentAILab/layout-dm"),
        model_summary=(
            f"{model_name} is a Diffusers-format LayoutDM checkpoint for "
            "conditional-free layout generation."
        ),
        developers="CyberAgentAILab released the original LayoutDM implementation.",
        model_type="Discrete diffusion model for layout generation.",
        base_model=(
            "Not applicable. This is a direct conversion of the original "
            "LayoutDM checkpoint, not a fine-tuned derivative of a Hub model."
        ),
        paper="https://arxiv.org/abs/2303.08137",
        preprocessing=(
            "The converted tokenizer represents each layout element as "
            "discrete category and bounding-box tokens. Bounding boxes use "
            "normalized center `xywh` coordinates and dataset-specific cluster "
            "centers stored with the tokenizer files."
        ),
        training_regime=(
            "Original LayoutDM training regime as released by the upstream "
            "project; this package converts the checkpoint and does not "
            "retrain it."
        ),
        testing_data=(
            "Reference parity tests use deterministic samples and forward-pass "
            "golden tensors generated from the original LayoutDM implementation "
            "for each converted dataset."
        ),
        testing_metrics=(
            "Tokenizer exact-match count, deterministic token-sequence "
            "exact-match count, and denoiser logits maximum absolute and "
            "relative error versus the original implementation."
        ),
        results_summary=(
            "The converted checkpoint matches the generated reference "
            "tensors exactly for tokenizer IO and deterministic sampling; "
            "denoiser logits are within the reported numeric tolerance."
        ),
        model_specs=(
            "LayoutDM models layout generation as discrete diffusion over "
            "category and bounding-box token sequences. This package exposes "
            "the denoiser, tokenizer, scheduler, and Diffusers pipeline needed "
            "to reproduce converted inference."
        ),
        compute_infrastructure=(
            "Conversion and parity generation run locally through the `uv` "
            "workspace commands documented in `models/layout-dm/README.md`."
        ),
        hardware_requirements=(
            "CPU is sufficient for package loading and conversion. CUDA is "
            "recommended for regenerating reference parity outputs and running "
            "the full parity test suite."
        ),
        software=(
            "Python 3.11+, PyTorch, Diffusers, Transformers, and the optional "
            "LayoutDM original-code dependencies declared by the `layout-dm` package."
        ),
        citation_apa=(
            "Inoue, N., Kikuchi, K., Simo-Serra, E., Otani, M., & Yamaguchi, K. "
            "(2023). LayoutDM: Discrete Diffusion Model for Controllable Layout "
            "Generation. CVPR."
        ),
    )

sanitize_for_yaml

sanitize_for_yaml(value: YamlInputValue) -> YamlValue

Convert enum-rich metadata into objects accepted by yaml.safe_dump.

Parameters:

Name Type Description Default
value YamlInputValue

Metadata value that may contain Enum instances, mappings, sequences, or dataclass instances.

required

Returns:

Type Description
YamlValue

A recursively sanitized value containing only YAML-safe scalar and

YamlValue

container types.

Examples:

>>> from laygen.common import DatasetName
>>> sanitize_for_yaml({"dataset": DatasetName.rico25})
{'dataset': 'rico25'}
Source code in lib/laygen/src/laygen/common/serialization.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def sanitize_for_yaml(value: YamlInputValue) -> YamlValue:
    """Convert enum-rich metadata into objects accepted by ``yaml.safe_dump``.

    Args:
        value: Metadata value that may contain ``Enum`` instances, mappings,
            sequences, or dataclass instances.

    Returns:
        A recursively sanitized value containing only YAML-safe scalar and
        container types.

    Examples:
        >>> from laygen.common import DatasetName
        >>> sanitize_for_yaml({"dataset": DatasetName.rico25})
        {'dataset': 'rico25'}
    """
    if isinstance(value, Enum):
        return str(value.value)
    if is_dataclass(value) and not isinstance(value, type):
        return sanitize_for_yaml(cast(YamlInputValue, asdict(value)))
    if isinstance(value, bytearray):
        return bytes(value)
    if isinstance(value, Mapping):
        return cast(
            YamlValue,
            {
                sanitize_for_yaml(cast(YamlInputValue, key)): sanitize_for_yaml(
                    cast(YamlInputValue, item)
                )
                for key, item in value.items()
            },
        )
    if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
        return [sanitize_for_yaml(cast(YamlInputValue, item)) for item in value]
    return cast(YamlScalar, value)

build_token_maps

build_token_maps(
    *,
    vocab_file: str | PathLike[str] | None,
    tokens: Sequence[str] | None,
    base_tokens: Sequence[str],
    numeric_id_vocab: bool = False,
) -> tuple[dict[str, int], dict[int, str]]

Build token/id maps from a JSON vocabulary file or synthetic token list.

Source code in lib/laygen/src/laygen/common/tokenization.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def build_token_maps(
    *,
    vocab_file: str | PathLike[str] | None,
    tokens: Sequence[str] | None,
    base_tokens: Sequence[str],
    numeric_id_vocab: bool = False,
) -> tuple[dict[str, int], dict[int, str]]:
    """Build token/id maps from a JSON vocabulary file or synthetic token list."""
    if vocab_file is not None:
        with Path(vocab_file).open() as f:
            raw_vocab = cast(dict[str, str | int], json.load(f))
        if numeric_id_vocab and all(str(key).isdigit() for key in raw_vocab):
            id2token = {int(key): str(value) for key, value in raw_vocab.items()}
            return {value: key for key, value in id2token.items()}, id2token
        token2id = {str(key): int(str(value)) for key, value in raw_vocab.items()}
        return token2id, {value: key for key, value in token2id.items()}

    token2id = {token: idx for idx, token in enumerate(base_tokens)}
    for token in tokens or ():
        if token not in token2id:
            token2id[token] = len(token2id)
    return token2id, {idx: token for token, idx in token2id.items()}

convert_id_to_token

convert_id_to_token(
    id2token: dict[int, str], index: int, unk_token: str
) -> str

Convert an id to a token using a tokenizer-local unknown token string.

Source code in lib/laygen/src/laygen/common/tokenization.py
46
47
48
def convert_id_to_token(id2token: dict[int, str], index: int, unk_token: str) -> str:
    """Convert an id to a token using a tokenizer-local unknown token string."""
    return id2token.get(int(index), unk_token)

convert_token_to_id

convert_token_to_id(
    token2id: dict[str, int], token: str, unk_token_id: int
) -> int

Convert a token to an id using a tokenizer-local unknown-token id.

Source code in lib/laygen/src/laygen/common/tokenization.py
41
42
43
def convert_token_to_id(token2id: dict[str, int], token: str, unk_token_id: int) -> int:
    """Convert a token to an id using a tokenizer-local unknown-token id."""
    return token2id.get(token, unk_token_id)

join_tokens

join_tokens(tokens: Sequence[str]) -> str

Join already-tokenized layout tokens with spaces.

Source code in lib/laygen/src/laygen/common/tokenization.py
51
52
53
def join_tokens(tokens: Sequence[str]) -> str:
    """Join already-tokenized layout tokens with spaces."""
    return " ".join(tokens)

save_json_vocabulary

save_json_vocabulary(
    *,
    save_directory: str | PathLike[str],
    filename: str,
    data: dict[str, int] | dict[str, str],
    filename_prefix: str | None = None,
) -> tuple[str]

Save tokenizer vocabulary JSON and return the generated path.

Source code in lib/laygen/src/laygen/common/tokenization.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def save_json_vocabulary(
    *,
    save_directory: str | PathLike[str],
    filename: str,
    data: dict[str, int] | dict[str, str],
    filename_prefix: str | None = None,
) -> tuple[str]:
    """Save tokenizer vocabulary JSON and return the generated path."""
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    name = filename if filename_prefix is None else f"{filename_prefix}-{filename}"
    out_path = out_dir / name
    with out_path.open("w") as f:
        json.dump(data, f, indent=2, sort_keys=True)
    return (str(out_path),)

split_whitespace_tokens

split_whitespace_tokens(text: str) -> list[str]

Split a layout token string on whitespace.

Source code in lib/laygen/src/laygen/common/tokenization.py
36
37
38
def split_whitespace_tokens(text: str) -> list[str]:
    """Split a layout token string on whitespace."""
    return text.strip().split()

bbox

Bounding-box conversion and quantization helpers for layout packages.

BoxFormat

Bases: StrEnum

Supported bounding-box coordinate formats.

Source code in lib/laygen/src/laygen/common/bbox.py
21
22
23
24
25
26
class BoxFormat(StrEnum):
    """Supported bounding-box coordinate formats."""

    xywh = auto()
    ltwh = auto()
    ltrb = auto()

normalize_box_format

normalize_box_format(
    box_format: BoxFormat | str,
) -> BoxFormat

Convert a public box-format value to BoxFormat.

Parameters:

Name Type Description Default
box_format BoxFormat | str

Box format enum or its string value.

required

Returns:

Type Description
BoxFormat

Normalized BoxFormat enum.

Raises:

Type Description
ValueError

If box_format is not supported.

Source code in lib/laygen/src/laygen/common/bbox.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def normalize_box_format(box_format: BoxFormat | str) -> BoxFormat:
    """Convert a public box-format value to ``BoxFormat``.

    Args:
        box_format: Box format enum or its string value.

    Returns:
        Normalized ``BoxFormat`` enum.

    Raises:
        ValueError: If ``box_format`` is not supported.
    """
    if isinstance(box_format, BoxFormat):
        return box_format
    try:
        return BoxFormat(box_format)
    except ValueError as exc:
        raise ValueError(f"Unsupported box_format: {box_format}") from exc

xywh_to_ltrb

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

Convert normalized center xywh boxes to ltrb boxes.

Parameters:

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

torch.Tensor with the last dimension ordered as center x, center y, width, and height.

required

Returns:

Type Description
Float[Tensor, '... 4']

torch.Tensor with the same leading shape and last dimension ordered as left,

Float[Tensor, '... 4']

top, right, and bottom.

Examples:

>>> import torch
>>> xywh_to_ltrb(torch.tensor([[0.5, 0.5, 0.2, 0.4]])).shape
torch.Size([1, 4])
Source code in lib/laygen/src/laygen/common/bbox.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def xywh_to_ltrb(bbox: Float[torch.Tensor, "... 4"]) -> Float[torch.Tensor, "... 4"]:
    """Convert normalized center ``xywh`` boxes to ``ltrb`` boxes.

    Args:
        bbox: torch.Tensor with the last dimension ordered as center x, center y,
            width, and height.

    Returns:
        torch.Tensor with the same leading shape and last dimension ordered as left,
        top, right, and bottom.

    Examples:
        >>> import torch
        >>> xywh_to_ltrb(torch.tensor([[0.5, 0.5, 0.2, 0.4]])).shape
        torch.Size([1, 4])
    """
    import torch

    x, y, w, h = bbox.unbind(dim=-1)
    return torch.stack((x - w / 2, y - h / 2, x + w / 2, y + h / 2), dim=-1)

ltrb_to_xywh

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

Convert ltrb boxes to normalized center xywh boxes.

Source code in lib/laygen/src/laygen/common/bbox.py
75
76
77
78
79
80
81
82
83
def ltrb_to_xywh(bbox: Float[torch.Tensor, "... 4"]) -> Float[torch.Tensor, "... 4"]:
    """Convert ``ltrb`` boxes to normalized center ``xywh`` boxes."""
    import torch

    left, top, right, bottom = bbox.unbind(dim=-1)
    return torch.stack(
        ((left + right) / 2, (top + bottom) / 2, right - left, bottom - top),
        dim=-1,
    )

ltwh_to_xywh

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

Convert left-top-width-height boxes to center xywh boxes.

Source code in lib/laygen/src/laygen/common/bbox.py
86
87
88
89
90
91
def ltwh_to_xywh(bbox: Float[torch.Tensor, "... 4"]) -> Float[torch.Tensor, "... 4"]:
    """Convert left-top-width-height boxes to center ``xywh`` boxes."""
    import torch

    left, top, width, height = bbox.unbind(dim=-1)
    return torch.stack((left + width / 2, top + height / 2, width, height), dim=-1)

xywh_to_ltwh

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

Convert center xywh boxes to left-top-width-height boxes.

Source code in lib/laygen/src/laygen/common/bbox.py
94
95
96
97
98
99
def xywh_to_ltwh(bbox: Float[torch.Tensor, "... 4"]) -> Float[torch.Tensor, "... 4"]:
    """Convert center ``xywh`` boxes to left-top-width-height boxes."""
    import torch

    x, y, w, h = bbox.unbind(dim=-1)
    return torch.stack((x - w / 2, y - h / 2, w, h), dim=-1)

clamp_boxes

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

Clamp normalized box coordinates into the inclusive [0, 1] range.

Source code in lib/laygen/src/laygen/common/bbox.py
102
103
104
def clamp_boxes(bbox: Float[torch.Tensor, "... 4"]) -> Float[torch.Tensor, "... 4"]:
    """Clamp normalized box coordinates into the inclusive ``[0, 1]`` range."""
    return bbox.clamp(0.0, 1.0)

normalize_boxes

normalize_boxes(
    bbox: Float[Tensor, "batch elements 4"],
    *,
    canvas_size: tuple[int, int],
    box_format: BoxFormat | str,
) -> Float[torch.Tensor, "batch elements 4"]

Normalize pixel boxes to center xywh coordinates.

Parameters:

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

torch.Tensor containing pixel-space boxes.

required
canvas_size tuple[int, int]

Canvas size as (width, height).

required
box_format BoxFormat | str

Input box format.

required

Returns:

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

torch.Tensor containing normalized center xywh boxes.

Raises:

Type Description
ValueError

If box_format is unsupported.

Examples:

>>> import torch
>>> normalize_boxes(
...     torch.tensor([[[0.0, 0.0, 10.0, 10.0]]]),
...     canvas_size=(100, 100),
...     box_format="ltrb",
... ).shape
torch.Size([1, 1, 4])
Source code in lib/laygen/src/laygen/common/bbox.py
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
def normalize_boxes(
    bbox: Float[torch.Tensor, "batch elements 4"],
    *,
    canvas_size: tuple[int, int],
    box_format: BoxFormat | str,
) -> Float[torch.Tensor, "batch elements 4"]:
    """Normalize pixel boxes to center ``xywh`` coordinates.

    Args:
        bbox: torch.Tensor containing pixel-space boxes.
        canvas_size: Canvas size as ``(width, height)``.
        box_format: Input box format.

    Returns:
        torch.Tensor containing normalized center ``xywh`` boxes.

    Raises:
        ValueError: If ``box_format`` is unsupported.

    Examples:
        >>> import torch
        >>> normalize_boxes(
        ...     torch.tensor([[[0.0, 0.0, 10.0, 10.0]]]),
        ...     canvas_size=(100, 100),
        ...     box_format="ltrb",
        ... ).shape
        torch.Size([1, 1, 4])
    """
    import torch

    bbox = bbox.to(dtype=torch.float32)
    scale = _canvas_tensor(canvas_size, bbox.device, bbox.dtype)
    normalized = bbox / scale
    fmt = normalize_box_format(box_format)
    if fmt is BoxFormat.xywh:
        return clamp_boxes(normalized)
    if fmt is BoxFormat.ltwh:
        return clamp_boxes(ltwh_to_xywh(normalized))
    if fmt is BoxFormat.ltrb:
        return clamp_boxes(ltrb_to_xywh(normalized))
    assert_never(fmt)

prepare_layout_tensors

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

Convert public layout arrays to batched normalized tensor inputs.

Parameters:

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

Layout boxes in box_format.

required
labels Int[Tensor, '...'] | Int[ndarray, '...'] | Sequence[Sequence[int]] | Sequence[int] | Sequence[ArrayLikeInput]

Integer labels matching the layout boxes.

required
mask Bool[Tensor, '...'] | Bool[ndarray, '...'] | Sequence[Sequence[bool]] | Sequence[bool] | Sequence[ArrayLikeInput] | None

Optional valid-element mask. All elements are valid when omitted.

None
box_format BoxFormat | str

Input box format.

xywh
normalized bool

Whether boxes are already normalized to [0, 1].

True
canvas_size tuple[int, int] | None

Pixel canvas size required when normalized=False.

None
clamp_converted_normalized bool

Whether normalized ltwh/ltrb conversion should clamp the resulting center xywh boxes to [0, 1].

False

Returns:

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

Batched bbox, labels, and mask tensors.

Raises:

Type Description
ValueError

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

Source code in lib/laygen/src/laygen/common/bbox.py
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
def prepare_layout_tensors(
    *,
    bbox: Float[torch.Tensor, "... 4"]
    | Float[np.ndarray, "... 4"]
    | Sequence[Sequence[Sequence[float]]]
    | Sequence[Sequence[float]]
    | Sequence[ArrayLikeInput],
    labels: Int[torch.Tensor, "..."]
    | Int[np.ndarray, "..."]
    | Sequence[Sequence[int]]
    | Sequence[int]
    | Sequence[ArrayLikeInput],
    mask: Bool[torch.Tensor, "..."]
    | Bool[np.ndarray, "..."]
    | Sequence[Sequence[bool]]
    | Sequence[bool]
    | Sequence[ArrayLikeInput]
    | None = None,
    box_format: BoxFormat | str = BoxFormat.xywh,
    normalized: bool = True,
    canvas_size: tuple[int, int] | None = None,
    clamp_converted_normalized: bool = False,
) -> tuple[
    Float[torch.Tensor, "batch elements 4"],
    Int[torch.Tensor, "batch elements"],
    Bool[torch.Tensor, "batch elements"],
]:
    """Convert public layout arrays to batched normalized tensor inputs.

    Args:
        bbox: Layout boxes in ``box_format``.
        labels: Integer labels matching the layout boxes.
        mask: Optional valid-element mask. All elements are valid when omitted.
        box_format: Input box format.
        normalized: Whether boxes are already normalized to ``[0, 1]``.
        canvas_size: Pixel canvas size required when ``normalized=False``.
        clamp_converted_normalized: Whether normalized ``ltwh``/``ltrb`` conversion
            should clamp the resulting center ``xywh`` boxes to ``[0, 1]``.

    Returns:
        Batched ``bbox``, ``labels``, and ``mask`` tensors.

    Raises:
        ValueError: If pixel-space boxes are passed without ``canvas_size`` or if
            ``box_format`` is unsupported.
    """
    import torch

    bbox_t = torch.as_tensor(bbox, dtype=torch.float32)
    labels_t = torch.as_tensor(labels, dtype=torch.long)
    if labels_t.ndim == 1:
        labels_t = labels_t.unsqueeze(0)
        bbox_t = bbox_t.unsqueeze(0)
    if mask is None:
        mask_t = torch.ones(labels_t.shape, dtype=torch.bool)
    else:
        mask_t = torch.as_tensor(mask, dtype=torch.bool)
        if mask_t.ndim == 1:
            mask_t = mask_t.unsqueeze(0)

    fmt = normalize_box_format(box_format)
    if not normalized:
        if canvas_size is None:
            raise ValueError("canvas_size is required when normalized=False")

        bbox_t = normalize_boxes(bbox_t, canvas_size=canvas_size, box_format=fmt)
    elif fmt is BoxFormat.ltwh:
        bbox_t = ltwh_to_xywh(bbox_t)
        if clamp_converted_normalized:
            bbox_t = clamp_boxes(bbox_t)
    elif fmt is BoxFormat.ltrb:
        bbox_t = ltrb_to_xywh(bbox_t)
        if clamp_converted_normalized:
            bbox_t = clamp_boxes(bbox_t)
    return bbox_t, labels_t, mask_t

denormalize_boxes

denormalize_boxes(
    bbox: Float[Tensor, "batch elements 4"],
    *,
    canvas_size: tuple[int, int],
    box_format: BoxFormat | str,
) -> Float[torch.Tensor, "batch elements 4"]

Convert normalized center xywh boxes to pixel-space boxes.

Parameters:

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

Normalized center xywh tensor.

required
canvas_size tuple[int, int]

Canvas size as (width, height).

required
box_format BoxFormat | str

Requested output box format.

required

Returns:

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

torch.Tensor in the requested pixel-space format.

Raises:

Type Description
ValueError

If box_format is unsupported.

Source code in lib/laygen/src/laygen/common/bbox.py
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
def denormalize_boxes(
    bbox: Float[torch.Tensor, "batch elements 4"],
    *,
    canvas_size: tuple[int, int],
    box_format: BoxFormat | str,
) -> Float[torch.Tensor, "batch elements 4"]:
    """Convert normalized center ``xywh`` boxes to pixel-space boxes.

    Args:
        bbox: Normalized center ``xywh`` tensor.
        canvas_size: Canvas size as ``(width, height)``.
        box_format: Requested output box format.

    Returns:
        torch.Tensor in the requested pixel-space format.

    Raises:
        ValueError: If ``box_format`` is unsupported.
    """
    fmt = normalize_box_format(box_format)
    if fmt is BoxFormat.xywh:
        out = bbox
    elif fmt is BoxFormat.ltwh:
        out = xywh_to_ltwh(bbox)
    elif fmt is BoxFormat.ltrb:
        out = xywh_to_ltrb(bbox)
    else:
        assert_never(fmt)
    scale = _canvas_tensor(canvas_size, out.device, out.dtype)
    return out * scale

linear_discretize

linear_discretize(
    values: Float[Tensor, "..."], *, num_bins: int
) -> Int[torch.Tensor, "..."]

Map normalized continuous values to evenly spaced integer bins.

Source code in lib/laygen/src/laygen/common/bbox.py
268
269
270
271
272
273
274
def linear_discretize(
    values: Float[torch.Tensor, "..."], *, num_bins: int
) -> Int[torch.Tensor, "..."]:
    """Map normalized continuous values to evenly spaced integer bins."""
    delta = 1.0 / num_bins
    values = values.clamp(0.0, 1.0 - delta)
    return (values * num_bins).round().long().clamp(0, num_bins - 1)

linear_continuize

linear_continuize(
    ids: Int[Tensor, "..."], *, num_bins: int
) -> Float[torch.Tensor, "..."]

Map evenly spaced integer bins back to normalized continuous values.

Source code in lib/laygen/src/laygen/common/bbox.py
277
278
279
280
281
def linear_continuize(
    ids: Int[torch.Tensor, "..."], *, num_bins: int
) -> Float[torch.Tensor, "..."]:
    """Map evenly spaced integer bins back to normalized continuous values."""
    return ids.float().clamp(0, num_bins - 1) / num_bins

conditions

Shared condition-type vocabulary for layout generation packages.

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()

ConditionAlias

Bases: StrEnum

Supported public and release-specific condition aliases.

Source code in lib/laygen/src/laygen/common/conditions.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class ConditionAlias(StrEnum):
    """Supported public and release-specific condition aliases."""

    unconditional = auto()
    uncond = auto()
    ugen = auto()
    random_generate = auto()
    label = auto()
    c = auto()
    cat_cond = auto()
    category_generate = auto()
    gen_t = auto()
    label_size = auto()
    cwh = auto()
    chw = auto()
    size_cond = auto()
    gen_ts = auto()
    completion = auto()
    partial = auto()
    complete = auto()
    elem_compl = auto()
    completion_generate = auto()
    refinement = auto()
    refine = auto()
    text = auto()
    prompt = auto()
    text_to_layout = auto()
    content_image = auto()
    content = auto()
    image = auto()
    visual = auto()
    relation = auto()
    scene_graph = auto()
    graph = auto()
    gen_r = auto()
    hierarchical = auto()
    hierarchy = auto()
    coarse_to_fine = auto()
    retrieval = auto()
    retrieved = auto()
    retrieval_examples = auto()

normalize_condition_type

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

Normalize condition aliases to a canonical ConditionType.

Parameters:

Name Type Description Default
condition_type ConditionType | str

Canonical condition enum or a public/release alias.

required

Returns:

Type Description
ConditionType

Canonical condition enum.

Raises:

Type Description
ValueError

If the condition type is unknown.

Examples:

>>> str(normalize_condition_type("gen_t"))
'label'
>>> str(normalize_condition_type("gen_r"))
'relation'
Source code in lib/laygen/src/laygen/common/conditions.py
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
def normalize_condition_type(condition_type: ConditionType | str) -> ConditionType:
    """Normalize condition aliases to a canonical ``ConditionType``.

    Args:
        condition_type: Canonical condition enum or a public/release alias.

    Returns:
        Canonical condition enum.

    Raises:
        ValueError: If the condition type is unknown.

    Examples:
        >>> str(normalize_condition_type("gen_t"))
        'label'
        >>> str(normalize_condition_type("gen_r"))
        'relation'
    """
    if isinstance(condition_type, ConditionType):
        return condition_type
    try:
        return _CONDITION_ALIASES[
            ConditionAlias(condition_type.lower().replace("-", "_"))
        ]
    except ValueError as exc:
        raise ValueError(f"Unknown condition_type: {condition_type}") from exc

discrete

Discrete diffusion tensor utilities shared by layout generators.

SamplingMode

Bases: StrEnum

Supported categorical sampling modes.

Source code in lib/laygen/src/laygen/common/discrete.py
21
22
23
24
25
26
27
28
29
class SamplingMode(StrEnum):
    """Supported categorical sampling modes."""

    deterministic = auto()
    random = auto()
    gumbel = auto()
    top_k = auto()
    top_p = auto()
    top_k_top_p = auto()

normalize_sampling_mode

normalize_sampling_mode(
    sampling: SamplingMode | str,
) -> SamplingMode

Convert a public sampling value to SamplingMode.

Parameters:

Name Type Description Default
sampling SamplingMode | str

Sampling enum or its string value.

required

Returns:

Type Description
SamplingMode

Normalized SamplingMode enum.

Raises:

Type Description
ValueError

If sampling is not supported.

Source code in lib/laygen/src/laygen/common/discrete.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
def normalize_sampling_mode(sampling: SamplingMode | str) -> SamplingMode:
    """Convert a public sampling value to ``SamplingMode``.

    Args:
        sampling: Sampling enum or its string value.

    Returns:
        Normalized ``SamplingMode`` enum.

    Raises:
        ValueError: If ``sampling`` is not supported.
    """
    if isinstance(sampling, SamplingMode):
        return sampling
    try:
        return SamplingMode(sampling)
    except ValueError as exc:
        raise ValueError(f"Unsupported sampling mode: {sampling}") from exc

index_to_log_onehot

index_to_log_onehot(
    input_ids: Int[Tensor, "batch ..."], vocab_size: int
) -> Float[torch.Tensor, "batch vocab ..."]

Convert categorical ids to log one-hot tensors.

Parameters:

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

Integer tensor with categorical ids.

required
vocab_size int

Size of the categorical vocabulary.

required

Returns:

Type Description
Float[Tensor, 'batch vocab ...']

Log one-hot tensor shaped (batch, vocab, ...).

Raises:

Type Description
ValueError

If any id is outside the vocabulary.

Examples:

>>> import torch
>>> index_to_log_onehot(torch.tensor([[0, 1]]), 3).shape
torch.Size([1, 3, 2])
Source code in lib/laygen/src/laygen/common/discrete.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
def index_to_log_onehot(
    input_ids: Int[torch.Tensor, "batch ..."], vocab_size: int
) -> Float[torch.Tensor, "batch vocab ..."]:
    """Convert categorical ids to log one-hot tensors.

    Args:
        input_ids: Integer tensor with categorical ids.
        vocab_size: Size of the categorical vocabulary.

    Returns:
        Log one-hot tensor shaped ``(batch, vocab, ...)``.

    Raises:
        ValueError: If any id is outside the vocabulary.

    Examples:
        >>> import torch
        >>> index_to_log_onehot(torch.tensor([[0, 1]]), 3).shape
        torch.Size([1, 3, 2])
    """
    import torch
    import torch.nn.functional as F

    if input_ids.numel() and input_ids.max().item() >= vocab_size:
        raise ValueError(
            f"input id {input_ids.max().item()} exceeds vocab_size {vocab_size}"
        )

    onehot = F.one_hot(input_ids.long(), vocab_size)
    order = (0, -1) + tuple(range(1, input_ids.ndim))
    return torch.log(onehot.permute(order).float().clamp(min=1e-30))

log_onehot_to_index

log_onehot_to_index(
    log_x: Float[Tensor, "batch vocab ..."],
) -> Int[torch.Tensor, "batch ..."]

Convert log one-hot tensors back to categorical ids.

Source code in lib/laygen/src/laygen/common/discrete.py
85
86
87
88
89
def log_onehot_to_index(
    log_x: Float[torch.Tensor, "batch vocab ..."],
) -> Int[torch.Tensor, "batch ..."]:
    """Convert log one-hot tensors back to categorical ids."""
    return log_x.argmax(dim=1)

multinomial_kl

multinomial_kl(
    log_prob1: Float[Tensor, "batch vocab tokens"],
    log_prob2: Float[Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]

Categorical KL divergence summed over the vocabulary dimension.

Parameters:

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

Log probabilities of the reference distribution.

required
log_prob2 Float[Tensor, 'batch vocab tokens']

Log probabilities of the compared distribution.

required

Returns:

Type Description
Float[Tensor, 'batch tokens']

Per-token KL divergence with the vocabulary dimension reduced.

Examples:

>>> import torch
>>> a = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
>>> float(multinomial_kl(a, a).sum())
0.0
Source code in lib/laygen/src/laygen/common/discrete.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def multinomial_kl(
    log_prob1: Float[torch.Tensor, "batch vocab tokens"],
    log_prob2: Float[torch.Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]:
    """Categorical KL divergence summed over the vocabulary dimension.

    Args:
        log_prob1: Log probabilities of the reference distribution.
        log_prob2: Log probabilities of the compared distribution.

    Returns:
        Per-token KL divergence with the vocabulary dimension reduced.

    Examples:
        >>> import torch
        >>> a = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
        >>> float(multinomial_kl(a, a).sum())
        0.0
    """
    return (log_prob1.exp() * (log_prob1 - log_prob2)).sum(dim=1)

log_categorical

log_categorical(
    log_x_start: Float[Tensor, "batch vocab tokens"],
    log_prob: Float[Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]

Categorical log-likelihood of log_x_start under log_prob.

Parameters:

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

Log one-hot targets.

required
log_prob Float[Tensor, 'batch vocab tokens']

Predicted log probabilities.

required

Returns:

Type Description
Float[Tensor, 'batch tokens']

Per-token log-likelihood with the vocabulary dimension reduced.

Examples:

>>> import torch
>>> target = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
>>> probs = torch.log(torch.tensor([[[0.25], [0.75]]]))
>>> log_categorical(target, probs).shape
torch.Size([1, 1])
Source code in lib/laygen/src/laygen/common/discrete.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def log_categorical(
    log_x_start: Float[torch.Tensor, "batch vocab tokens"],
    log_prob: Float[torch.Tensor, "batch vocab tokens"],
) -> Float[torch.Tensor, "batch tokens"]:
    """Categorical log-likelihood of ``log_x_start`` under ``log_prob``.

    Args:
        log_x_start: Log one-hot targets.
        log_prob: Predicted log probabilities.

    Returns:
        Per-token log-likelihood with the vocabulary dimension reduced.

    Examples:
        >>> import torch
        >>> target = torch.log(torch.tensor([[[1.0], [0.0]]]).clamp_min(1e-30))
        >>> probs = torch.log(torch.tensor([[[0.25], [0.75]]]))
        >>> log_categorical(target, probs).shape
        torch.Size([1, 1])
    """
    return (log_x_start.exp() * log_prob).sum(dim=1)

sample_time_importance

sample_time_importance(
    batch_size: int,
    *,
    num_timesteps: int,
    lt_history: Float[Tensor, "timesteps"],
    lt_count: Float[Tensor, "timesteps"],
    generator: Generator | None = None,
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Sample diffusion timesteps with loss-aware importance sampling.

Until every timestep bucket has more than ten observations the sampler falls back to a uniform draw. Afterwards timesteps are drawn proportionally to the square root of the running squared-loss history.

Parameters:

Name Type Description Default
batch_size int

Number of timesteps to draw.

required
num_timesteps int

Total diffusion timesteps.

required
lt_history Float[Tensor, 'timesteps']

Running squared-loss history buffer.

required
lt_count Float[Tensor, 'timesteps']

Per-timestep observation-count buffer.

required
generator Generator | None

Optional random generator for deterministic draws.

None

Returns:

Type Description
tuple[Int[Tensor, 'batch'], Float[Tensor, 'batch']]

Sampled timesteps and their sampling probabilities.

Examples:

>>> import torch
>>> hist = torch.arange(1, 5, dtype=torch.float32)
>>> count = torch.full((4,), 11.0)
>>> gen = torch.Generator().manual_seed(0)
>>> t, pt = sample_time_importance(
...     2, num_timesteps=4, lt_history=hist, lt_count=count, generator=gen
... )
>>> t.shape, pt.shape
(torch.Size([2]), torch.Size([2]))
Source code in lib/laygen/src/laygen/common/discrete.py
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
def sample_time_importance(
    batch_size: int,
    *,
    num_timesteps: int,
    lt_history: Float[torch.Tensor, "timesteps"],
    lt_count: Float[torch.Tensor, "timesteps"],
    generator: torch.Generator | None = None,
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Sample diffusion timesteps with loss-aware importance sampling.

    Until every timestep bucket has more than ten observations the sampler falls
    back to a uniform draw. Afterwards timesteps are drawn proportionally to the
    square root of the running squared-loss history.

    Args:
        batch_size: Number of timesteps to draw.
        num_timesteps: Total diffusion timesteps.
        lt_history: Running squared-loss history buffer.
        lt_count: Per-timestep observation-count buffer.
        generator: Optional random generator for deterministic draws.

    Returns:
        Sampled timesteps and their sampling probabilities.

    Examples:
        >>> import torch
        >>> hist = torch.arange(1, 5, dtype=torch.float32)
        >>> count = torch.full((4,), 11.0)
        >>> gen = torch.Generator().manual_seed(0)
        >>> t, pt = sample_time_importance(
        ...     2, num_timesteps=4, lt_history=hist, lt_count=count, generator=gen
        ... )
        >>> t.shape, pt.shape
        (torch.Size([2]), torch.Size([2]))
    """
    import torch

    device = lt_history.device
    if not bool((lt_count > 10).all()):
        return sample_time_uniform(
            batch_size,
            num_timesteps=num_timesteps,
            device=device,
            generator=generator,
        )
    lt_sqrt = torch.sqrt(lt_history + 1e-10) + 0.0001
    lt_sqrt[0] = lt_sqrt[1]
    pt_all = lt_sqrt / lt_sqrt.sum()
    t = torch.multinomial(
        pt_all, num_samples=batch_size, replacement=True, generator=generator
    )
    pt = pt_all.gather(dim=0, index=t)
    return t, pt

sample_time_uniform

sample_time_uniform(
    batch_size: int,
    *,
    num_timesteps: int,
    device: device,
    generator: Generator | None = None,
) -> tuple[
    Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]
]

Sample diffusion timesteps uniformly.

Parameters:

Name Type Description Default
batch_size int

Number of timesteps to draw.

required
num_timesteps int

Total diffusion timesteps.

required
device device

Device for the sampled tensors.

required
generator Generator | None

Optional random generator for deterministic draws.

None

Returns:

Type Description
tuple[Int[Tensor, 'batch'], Float[Tensor, 'batch']]

Sampled timesteps and their uniform sampling probabilities.

Examples:

>>> import torch
>>> gen = torch.Generator().manual_seed(0)
>>> t, pt = sample_time_uniform(
...     2, num_timesteps=4, device=torch.device("cpu"), generator=gen
... )
>>> t.shape, pt.tolist()
(torch.Size([2]), [0.25, 0.25])
Source code in lib/laygen/src/laygen/common/discrete.py
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
def sample_time_uniform(
    batch_size: int,
    *,
    num_timesteps: int,
    device: torch.device,
    generator: torch.Generator | None = None,
) -> tuple[Int[torch.Tensor, "batch"], Float[torch.Tensor, "batch"]]:
    """Sample diffusion timesteps uniformly.

    Args:
        batch_size: Number of timesteps to draw.
        num_timesteps: Total diffusion timesteps.
        device: Device for the sampled tensors.
        generator: Optional random generator for deterministic draws.

    Returns:
        Sampled timesteps and their uniform sampling probabilities.

    Examples:
        >>> import torch
        >>> gen = torch.Generator().manual_seed(0)
        >>> t, pt = sample_time_uniform(
        ...     2, num_timesteps=4, device=torch.device("cpu"), generator=gen
        ... )
        >>> t.shape, pt.tolist()
        (torch.Size([2]), [0.25, 0.25])
    """
    import torch

    t = torch.randint(
        0, num_timesteps, (batch_size,), device=device, generator=generator
    ).long()
    pt = torch.ones_like(t).float() / num_timesteps
    return t, pt

update_loss_history

update_loss_history(
    kl_loss: Float[Tensor, "batch"],
    t: Int[Tensor, "batch"],
    lt_history: Float[Tensor, "timesteps"],
    lt_count: Float[Tensor, "timesteps"],
) -> None

Update squared-loss history buffers in place.

The update matches the D3PM-style training loop used by LayoutDM and LayoutDiffusion: each sampled timestep receives 0.1 * loss**2 + 0.9 times the previous bucket value, and the observation count is incremented.

Parameters:

Name Type Description Default
kl_loss Float[Tensor, 'batch']

Per-example KL or decoder loss for the sampled timesteps.

required
t Int[Tensor, 'batch']

Sampled timestep ids for each example.

required
lt_history Float[Tensor, 'timesteps']

Running squared-loss history buffer to mutate.

required
lt_count Float[Tensor, 'timesteps']

Per-timestep observation count buffer to mutate.

required

Returns:

Type Description
None

None. The history and count tensors are updated in place.

Examples:

>>> import torch
>>> history = torch.zeros(3)
>>> count = torch.zeros(3)
>>> update_loss_history(torch.tensor([2.0]), torch.tensor([1]), history, count)
>>> history.tolist(), count.tolist()
([0.0, 0.4000000059604645, 0.0], [0.0, 1.0, 0.0])
Source code in lib/laygen/src/laygen/common/discrete.py
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
def update_loss_history(
    kl_loss: Float[torch.Tensor, "batch"],
    t: Int[torch.Tensor, "batch"],
    lt_history: Float[torch.Tensor, "timesteps"],
    lt_count: Float[torch.Tensor, "timesteps"],
) -> None:
    """Update squared-loss history buffers in place.

    The update matches the D3PM-style training loop used by LayoutDM and
    LayoutDiffusion: each sampled timestep receives ``0.1 * loss**2 + 0.9``
    times the previous bucket value, and the observation count is incremented.

    Args:
        kl_loss: Per-example KL or decoder loss for the sampled timesteps.
        t: Sampled timestep ids for each example.
        lt_history: Running squared-loss history buffer to mutate.
        lt_count: Per-timestep observation count buffer to mutate.

    Returns:
        None. The history and count tensors are updated in place.

    Examples:
        >>> import torch
        >>> history = torch.zeros(3)
        >>> count = torch.zeros(3)
        >>> update_loss_history(torch.tensor([2.0]), torch.tensor([1]), history, count)
        >>> history.tolist(), count.tolist()
        ([0.0, 0.4000000059604645, 0.0], [0.0, 1.0, 0.0])
    """
    import torch

    lt2 = kl_loss.pow(2)
    lt2_prev = lt_history.gather(dim=0, index=t)
    new_history = (0.1 * lt2 + 0.9 * lt2_prev).detach()
    lt_history.scatter_(dim=0, index=t, src=new_history)
    lt_count.scatter_add_(dim=0, index=t, src=torch.ones_like(lt2))

log_add_exp

log_add_exp(
    a: Float[Tensor, "..."], b: Float[Tensor, "..."]
) -> Float[torch.Tensor, "..."]

Compute a numerically stable elementwise log(exp(a) + exp(b)).

Source code in lib/laygen/src/laygen/common/discrete.py
266
267
268
269
270
271
272
273
def log_add_exp(
    a: Float[torch.Tensor, "..."], b: Float[torch.Tensor, "..."]
) -> Float[torch.Tensor, "..."]:
    """Compute a numerically stable elementwise ``log(exp(a) + exp(b))``."""
    import torch

    maximum = torch.maximum(a, b)
    return maximum + torch.log(torch.exp(a - maximum) + torch.exp(b - maximum))

extract

extract(
    values: Float[Tensor, "timesteps"],
    timesteps: Int[Tensor, "batch"],
    broadcast_shape: Size,
) -> Float[torch.Tensor, "batch ..."]

Gather timestep values and reshape them for broadcast operations.

Source code in lib/laygen/src/laygen/common/discrete.py
276
277
278
279
280
281
282
283
284
def extract(
    values: Float[torch.Tensor, "timesteps"],
    timesteps: Int[torch.Tensor, "batch"],
    broadcast_shape: torch.Size,
) -> Float[torch.Tensor, "batch ..."]:
    """Gather timestep values and reshape them for broadcast operations."""
    batch, *_ = timesteps.shape
    out = values.to(timesteps.device).gather(-1, timesteps)
    return out.reshape(batch, *((1,) * (len(broadcast_shape) - 1)))

gumbel_noise_like

gumbel_noise_like(
    x: Float[Tensor, "..."],
    *,
    generator: Generator | None = None,
) -> Float[torch.Tensor, "..."]

Sample Gumbel noise with the same shape, dtype, and device as x.

Source code in lib/laygen/src/laygen/common/discrete.py
287
288
289
290
291
292
293
294
295
296
def gumbel_noise_like(
    x: Float[torch.Tensor, "..."],
    *,
    generator: torch.Generator | None = None,
) -> Float[torch.Tensor, "..."]:
    """Sample Gumbel noise with the same shape, dtype, and device as ``x``."""
    import torch

    uniform = torch.rand(x.shape, device=x.device, dtype=x.dtype, generator=generator)
    return -torch.log(-torch.log(uniform + 1e-30) + 1e-30)

log_sample_categorical

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

Sample categorical ids from log probabilities with Gumbel-max.

Source code in lib/laygen/src/laygen/common/discrete.py
299
300
301
302
303
304
305
def log_sample_categorical(
    logits: Float[torch.Tensor, "batch vocab ..."],
    *,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch ..."]:
    """Sample categorical ids from log probabilities with Gumbel-max."""
    return (logits + gumbel_noise_like(logits, generator=generator)).argmax(dim=1)

top_k_logits

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

Mask logits outside the top-k entries along dim.

Source code in lib/laygen/src/laygen/common/discrete.py
308
309
310
311
312
313
314
315
316
317
318
def top_k_logits(
    logits: Float[torch.Tensor, "... vocab"], k: int, dim: int = -1
) -> Float[torch.Tensor, "... vocab"]:
    """Mask logits outside the top-k entries along ``dim``."""
    import torch

    if k <= 0 or k >= logits.size(dim):
        return logits
    values = torch.topk(logits, k, dim=dim).values
    threshold = values.select(dim, k - 1).unsqueeze(dim)
    return logits.masked_fill(logits < threshold, LOG_EPS)

sample_categorical

sample_categorical(
    logits: Float[Tensor, "... vocab"],
    *,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int | None = None,
    top_p: float | None = None,
    generator: Generator | None = None,
) -> Int[torch.Tensor, "batch ..."]

Sample categorical ids from logits using LayoutDM sampling modes.

Parameters:

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

torch.Tensor whose last dimension is the categorical vocabulary.

required
sampling SamplingMode | str

Sampling mode name.

random
temperature float

Positive temperature used before random sampling.

1.0
top_k int | None

Number of logits retained for top-k modes.

None
top_p float | None

Cumulative probability retained for top-p modes.

None
generator Generator | None

Optional torch generator for deterministic sampling.

None

Returns:

Type Description
Int[Tensor, 'batch ...']

torch.Tensor of sampled ids with shape logits.shape[:-1].

Examples:

>>> import torch
>>> sample_categorical(
...     torch.tensor([[[0.0, 1.0]]]),
...     sampling="deterministic",
... )
tensor([[1]])
Source code in lib/laygen/src/laygen/common/discrete.py
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
def sample_categorical(
    logits: Float[torch.Tensor, "... vocab"],
    *,
    sampling: SamplingMode | str = SamplingMode.random,
    temperature: float = 1.0,
    top_k: int | None = None,
    top_p: float | None = None,
    generator: torch.Generator | None = None,
) -> Int[torch.Tensor, "batch ..."]:
    """Sample categorical ids from logits using LayoutDM sampling modes.

    Args:
        logits: torch.Tensor whose last dimension is the categorical vocabulary.
        sampling: Sampling mode name.
        temperature: Positive temperature used before random sampling.
        top_k: Number of logits retained for top-k modes.
        top_p: Cumulative probability retained for top-p modes.
        generator: Optional torch generator for deterministic sampling.

    Returns:
        torch.Tensor of sampled ids with shape ``logits.shape[:-1]``.

    Examples:
        >>> import torch
        >>> sample_categorical(
        ...     torch.tensor([[[0.0, 1.0]]]),
        ...     sampling="deterministic",
        ... )
        tensor([[1]])
    """
    import torch

    mode = normalize_sampling_mode(sampling)
    match mode:
        case SamplingMode.deterministic:
            return logits.argmax(dim=-1)
        case SamplingMode.random:
            scaled = logits / temperature
        case SamplingMode.gumbel:
            scaled = logits / temperature
            return (scaled + gumbel_noise_like(scaled, generator=generator)).argmax(
                dim=-1
            )
        case SamplingMode.top_k:
            scaled = logits / temperature
            if top_k is not None:
                scaled = top_k_logits(scaled, top_k, dim=-1)
        case SamplingMode.top_p:
            scaled = logits / temperature
            if top_p is not None:
                scaled = _top_p_logits(scaled, top_p)
        case SamplingMode.top_k_top_p:
            scaled = logits / temperature
            if top_k is not None:
                scaled = top_k_logits(scaled, top_k, dim=-1)
            if top_p is not None:
                scaled = _top_p_logits(scaled, top_p)
        case _:
            assert_never(mode)
    probs = scaled.softmax(dim=-1).reshape(-1, scaled.size(-1))
    sampled = torch.multinomial(probs, 1, generator=generator).reshape(
        scaled.shape[:-1]
    )
    return sampled

batch_topk_mask

batch_topk_mask(
    scores: Float[Tensor, "batch candidates"],
    k: Int[Tensor, "batch"],
) -> Bool[torch.Tensor, "batch candidates"]

Return a per-row boolean mask for the top k scores.

Source code in lib/laygen/src/laygen/common/discrete.py
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
def batch_topk_mask(
    scores: Float[torch.Tensor, "batch candidates"], k: Int[torch.Tensor, "batch"]
) -> Bool[torch.Tensor, "batch candidates"]:
    """Return a per-row boolean mask for the top ``k`` scores."""
    import torch

    if scores.ndim != 2:
        raise ValueError("scores must be rank-2")

    max_k = int(k.max().item()) if k.numel() else 0
    if max_k == 0:
        return torch.zeros_like(scores, dtype=torch.bool)
    _, indices = torch.topk(scores, max_k, dim=1)
    ranks = torch.arange(max_k, device=scores.device).unsqueeze(0)
    active = ranks < k.to(scores.device).unsqueeze(1)
    mask = torch.zeros_like(scores, dtype=torch.bool)
    return mask.scatter(1, indices, active)

enums

Shared enum normalization helpers.

normalize_enum_value

normalize_enum_value(
    value: EnumT | str,
    enum_type: type[EnumT],
    *,
    option_name: str,
) -> EnumT

Normalize a public string-or-enum option to a StrEnum value.

Source code in lib/laygen/src/laygen/common/enums.py
11
12
13
14
15
16
17
18
19
20
21
22
23
def normalize_enum_value(
    value: EnumT | str,
    enum_type: type[EnumT],
    *,
    option_name: str,
) -> EnumT:
    """Normalize a public string-or-enum option to a ``StrEnum`` value."""
    if isinstance(value, enum_type):
        return value
    try:
        return enum_type(value)
    except ValueError as exc:
        raise ValueError(f"Unsupported {option_name}: {value}") from exc

labels

Dataset label registries shared by layout generation packages.

DatasetName

Bases: StrEnum

Canonical dataset names supported by the shared label registry.

Source code in lib/laygen/src/laygen/common/labels.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class DatasetName(StrEnum):
    """Canonical dataset names supported by the shared label registry."""

    rico25 = auto()
    rico13 = auto()
    publaynet = auto()
    magazine = auto()
    nsr_1k = "nsr-1k"
    grit = auto()
    coco = auto()
    vg_msdn = "vg-msdn"
    coco_grounded = "coco-grounded"
    web = auto()
    webui = auto()
    housegan_floorplan_vectorized = "housegan-floorplan-vectorized"

Rico25Label

Bases: StrEnum

RICO25 label names in dataset id order.

Source code in lib/laygen/src/laygen/common/labels.py
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Rico25Label(StrEnum):
    """RICO25 label names in dataset id order."""

    text = "Text"
    image = "Image"
    icon = "Icon"
    text_button = "Text Button"
    list_item = "List Item"
    input = "Input"
    background_image = "Background Image"
    card = "Card"
    web_view = "Web View"
    radio_button = "Radio Button"
    drawer = "Drawer"
    checkbox = "Checkbox"
    advertisement = "Advertisement"
    modal = "Modal"
    pager_indicator = "Pager Indicator"
    slider = "Slider"
    on_off_switch = "On/Off Switch"
    button_bar = "Button Bar"
    toolbar = "Toolbar"
    number_stepper = "Number Stepper"
    multi_tab = "Multi-Tab"
    date_picker = "Date Picker"
    map_view = "Map View"
    video = "Video"
    bottom_navigation = "Bottom Navigation"

Rico13Label

Bases: StrEnum

RICO13 label names in dataset id order.

Source code in lib/laygen/src/laygen/common/labels.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Rico13Label(StrEnum):
    """RICO13 label names in dataset id order."""

    text = "Text"
    image = "Image"
    icon = "Icon"
    text_button = "Text Button"
    list_item = "List Item"
    input = "Input"
    background_image = "Background Image"
    card = "Card"
    web_view = "Web View"
    radio_button = "Radio Button"
    drawer = "Drawer"
    checkbox = "Checkbox"
    advertisement = "Advertisement"

PubLayNetLabel

Bases: StrEnum

PubLayNet label names in dataset id order.

Source code in lib/laygen/src/laygen/common/labels.py
74
75
76
77
78
79
80
81
class PubLayNetLabel(StrEnum):
    """PubLayNet label names in dataset id order."""

    text = "text"
    title = "title"
    list = "list"
    table = "table"
    figure = "figure"

MagazineLabel

Bases: StrEnum

Magazine label names in dataset id order.

Source code in lib/laygen/src/laygen/common/labels.py
84
85
86
87
88
89
90
91
class MagazineLabel(StrEnum):
    """Magazine label names in dataset id order."""

    text = "text"
    image = "image"
    headline = "headline"
    text_over_image = "text-over-image"
    headline_over_image = "headline-over-image"

DatasetMetadata

Bases: TypedDict

Shared metadata keyed by canonical dataset name.

Source code in lib/laygen/src/laygen/common/labels.py
94
95
96
97
98
class DatasetMetadata(TypedDict):
    """Shared metadata keyed by canonical dataset name."""

    labels: tuple[StrEnum, ...]
    max_elements: int

normalize_dataset_name

normalize_dataset_name(
    dataset_name: DatasetName | str,
) -> DatasetName

Normalize common dataset aliases to canonical registry names.

Parameters:

Name Type Description Default
dataset_name DatasetName | str

User-facing dataset name or release alias.

required

Returns:

Type Description
DatasetName

Canonical dataset name used by the shared label registry.

Raises:

Type Description
ValueError

If the dataset name is unknown.

Examples:

>>> str(normalize_dataset_name("rico25_max25"))
'rico25'
Source code in lib/laygen/src/laygen/common/labels.py
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def normalize_dataset_name(dataset_name: DatasetName | str) -> DatasetName:
    """Normalize common dataset aliases to canonical registry names.

    Args:
        dataset_name: User-facing dataset name or release alias.

    Returns:
        Canonical dataset name used by the shared label registry.

    Raises:
        ValueError: If the dataset name is unknown.

    Examples:
        >>> str(normalize_dataset_name("rico25_max25"))
        'rico25'
    """
    if isinstance(dataset_name, DatasetName):
        return dataset_name
    key = dataset_name.lower().replace("-", "_")
    try:
        return _ALIASES[key]
    except KeyError as exc:
        raise ValueError(f"Unknown dataset_name: {dataset_name}") from exc

labels_for_dataset

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

Return the ordered label vocabulary for a dataset.

Source code in lib/laygen/src/laygen/common/labels.py
186
187
188
189
def labels_for_dataset(dataset_name: DatasetName | str) -> tuple[str, ...]:
    """Return the ordered label vocabulary for a dataset."""
    metadata = DATASET_METADATA[normalize_dataset_name(dataset_name)]
    return tuple(str(label) for label in metadata["labels"])

id2label_for_dataset

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

Return an integer-id to label-name mapping for a dataset.

Source code in lib/laygen/src/laygen/common/labels.py
192
193
194
def id2label_for_dataset(dataset_name: DatasetName | str) -> dict[int, str]:
    """Return an integer-id to label-name mapping for a dataset."""
    return dict(enumerate(labels_for_dataset(dataset_name)))

label2id_for_dataset

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

Return a label-name to integer-id mapping for a dataset.

Source code in lib/laygen/src/laygen/common/labels.py
197
198
199
def label2id_for_dataset(dataset_name: DatasetName | str) -> dict[str, int]:
    """Return a label-name to integer-id mapping for a dataset."""
    return {label: i for i, label in id2label_for_dataset(dataset_name).items()}

max_elements_for_dataset

max_elements_for_dataset(
    dataset_name: DatasetName | str,
) -> int

Return the shared maximum element count for a dataset.

Source code in lib/laygen/src/laygen/common/labels.py
202
203
204
def max_elements_for_dataset(dataset_name: DatasetName | str) -> int:
    """Return the shared maximum element count for a dataset."""
    return DATASET_METADATA[normalize_dataset_name(dataset_name)]["max_elements"]

layout_keys

Shared key names for Hugging Face layout sample extraction.

model_card

Model-card builders shared by converted layout model packages.

ModelCardMetadataKey

Bases: StrEnum

YAML metadata keys emitted by generated Hub model cards.

Source code in lib/laygen/src/laygen/common/model_card.py
16
17
18
19
20
21
22
23
24
25
class ModelCardMetadataKey(StrEnum):
    """YAML metadata keys emitted by generated Hub model cards."""

    model_name = auto()
    license = auto()
    library_name = auto()
    pipeline_tag = auto()
    tags = auto()
    datasets = auto()
    language = auto()

ModelCardMetadata

Bases: TypedDict

Structured metadata passed to ModelCardData.

Source code in lib/laygen/src/laygen/common/model_card.py
33
34
35
36
37
38
39
40
41
42
class ModelCardMetadata(TypedDict):
    """Structured metadata passed to ``ModelCardData``."""

    model_name: str
    license: str
    library_name: str
    pipeline_tag: str
    tags: list[str]
    datasets: list[str]
    language: list[str]

ParityMetricKey

Bases: StrEnum

Column keys used in generated parity metric rows.

Source code in lib/laygen/src/laygen/common/model_card.py
45
46
47
48
49
50
51
52
class ParityMetricKey(StrEnum):
    """Column keys used in generated parity metric rows."""

    dataset = auto()
    tokenizer_exact = auto()
    deterministic_exact = auto()
    logits_max_abs = auto()
    logits_max_rel = auto()

ParityMetric dataclass

Reference-parity metric row included in generated model cards.

Attributes:

Name Type Description
dataset str

Dataset or checkpoint name.

tokenizer_exact str

Exact-match ratio for tokenizer round-trips.

deterministic_exact str

Exact-match ratio for deterministic samples.

logits_max_abs float

Maximum absolute denoiser-logit difference.

logits_max_rel float

Maximum relative denoiser-logit difference.

Source code in lib/laygen/src/laygen/common/model_card.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@dataclass(frozen=True)
class ParityMetric:
    """Reference-parity metric row included in generated model cards.

    Attributes:
        dataset: Dataset or checkpoint name.
        tokenizer_exact: Exact-match ratio for tokenizer round-trips.
        deterministic_exact: Exact-match ratio for deterministic samples.
        logits_max_abs: Maximum absolute denoiser-logit difference.
        logits_max_rel: Maximum relative denoiser-logit difference.
    """

    dataset: str
    tokenizer_exact: str
    deterministic_exact: str
    logits_max_abs: float
    logits_max_rel: float

ParityMetricRow

Bases: TypedDict

Structured parity metric row accepted by model-card generation.

Source code in lib/laygen/src/laygen/common/model_card.py
77
78
79
80
81
82
83
84
class ParityMetricRow(TypedDict):
    """Structured parity metric row accepted by model-card generation."""

    dataset: str
    tokenizer_exact: str
    deterministic_exact: str
    logits_max_abs: float
    logits_max_rel: float

build_layout_model_card

build_layout_model_card(
    *,
    model_id: str,
    model_name: str,
    dataset_ids: Sequence[str],
    license: str,
    library_name: str,
    pipeline_tag: str,
    tags: Sequence[str],
    model_details: str,
    intended_uses: str,
    limitations: str,
    how_to_use: str,
    training_data: str,
    parity_metrics: Sequence[ParityMetricInput],
    citation_bibtex: str,
    original_implementation_url: str,
    model_summary: str | None = None,
    developers: str | None = None,
    model_type: str = "Layout generation model.",
    base_model: str | None = None,
    paper: str | None = None,
    preprocessing: str | None = None,
    training_regime: str | None = None,
    testing_data: str | None = None,
    testing_metrics: str | None = None,
    results_summary: str | None = None,
    model_specs: str | None = None,
    compute_infrastructure: str | None = None,
    hardware_requirements: str | None = None,
    software: str | None = None,
    citation_apa: str | None = None,
) -> ModelCard

Build a Hugging Face model card for a layout-generation checkpoint.

Parameters:

Name Type Description Default
model_id str

Hub model id displayed in the card title.

required
model_name str

Human-readable model name.

required
dataset_ids Sequence[str]

Hub dataset ids used by the checkpoint.

required
license str

SPDX-style license id for YAML metadata.

required
library_name str

Hub library name, such as diffusers.

required
pipeline_tag str

Hub task tag.

required
tags Sequence[str]

Additional Hub tags.

required
model_details str

User-facing model description.

required
intended_uses str

Direct-use description.

required
limitations str

Known limitations and risks.

required
how_to_use str

Python snippet without surrounding fences.

required
training_data str

Training-data description.

required
parity_metrics Sequence[ParityMetricInput]

Parity table rows.

required
citation_bibtex str

BibTeX citation without surrounding fences.

required
original_implementation_url str

URL for the upstream implementation.

required
model_summary str | None

Short model summary for the card header.

None
developers str | None

Original developer attribution.

None
model_type str

User-facing model family/type.

'Layout generation model.'
base_model str | None

Base-model or conversion-relationship statement.

None
paper str | None

Paper URL.

None
preprocessing str | None

Preprocessing description.

None
training_regime str | None

Training-regime description.

None
testing_data str | None

Evaluation data or fixture description.

None
testing_metrics str | None

Evaluation metric description.

None
results_summary str | None

Summary of recorded parity results.

None
model_specs str | None

Architecture and objective summary.

None
compute_infrastructure str | None

Conversion/parity compute description.

None
hardware_requirements str | None

Runtime and parity hardware requirements.

None
software str | None

Runtime and parity software requirements.

None
citation_apa str | None

Optional APA-style citation.

None

Returns:

Type Description
ModelCard

Rendered huggingface_hub.ModelCard instance with structured

ModelCard

metadata attached.

Examples:

>>> card = layoutdm_model_card(dataset="rico25")
>>> card.data.to_dict()["library_name"]
'diffusers'
Source code in lib/laygen/src/laygen/common/model_card.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
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
def build_layout_model_card(
    *,
    model_id: str,
    model_name: str,
    dataset_ids: Sequence[str],
    license: str,
    library_name: str,
    pipeline_tag: str,
    tags: Sequence[str],
    model_details: str,
    intended_uses: str,
    limitations: str,
    how_to_use: str,
    training_data: str,
    parity_metrics: Sequence[ParityMetricInput],
    citation_bibtex: str,
    original_implementation_url: str,
    model_summary: str | None = None,
    developers: str | None = None,
    model_type: str = "Layout generation model.",
    base_model: str | None = None,
    paper: str | None = None,
    preprocessing: str | None = None,
    training_regime: str | None = None,
    testing_data: str | None = None,
    testing_metrics: str | None = None,
    results_summary: str | None = None,
    model_specs: str | None = None,
    compute_infrastructure: str | None = None,
    hardware_requirements: str | None = None,
    software: str | None = None,
    citation_apa: str | None = None,
) -> ModelCard:
    """Build a Hugging Face model card for a layout-generation checkpoint.

    Args:
        model_id: Hub model id displayed in the card title.
        model_name: Human-readable model name.
        dataset_ids: Hub dataset ids used by the checkpoint.
        license: SPDX-style license id for YAML metadata.
        library_name: Hub library name, such as ``diffusers``.
        pipeline_tag: Hub task tag.
        tags: Additional Hub tags.
        model_details: User-facing model description.
        intended_uses: Direct-use description.
        limitations: Known limitations and risks.
        how_to_use: Python snippet without surrounding fences.
        training_data: Training-data description.
        parity_metrics: Parity table rows.
        citation_bibtex: BibTeX citation without surrounding fences.
        original_implementation_url: URL for the upstream implementation.
        model_summary: Short model summary for the card header.
        developers: Original developer attribution.
        model_type: User-facing model family/type.
        base_model: Base-model or conversion-relationship statement.
        paper: Paper URL.
        preprocessing: Preprocessing description.
        training_regime: Training-regime description.
        testing_data: Evaluation data or fixture description.
        testing_metrics: Evaluation metric description.
        results_summary: Summary of recorded parity results.
        model_specs: Architecture and objective summary.
        compute_infrastructure: Conversion/parity compute description.
        hardware_requirements: Runtime and parity hardware requirements.
        software: Runtime and parity software requirements.
        citation_apa: Optional APA-style citation.

    Returns:
        Rendered ``huggingface_hub.ModelCard`` instance with structured
        metadata attached.

    Examples:
        >>> card = layoutdm_model_card(dataset="rico25")
        >>> card.data.to_dict()["library_name"]
        'diffusers'
    """
    metadata = _model_card_metadata(
        model_name=model_name,
        license=license,
        library_name=library_name,
        pipeline_tag=pipeline_tag,
        tags=tags,
        dataset_ids=dataset_ids,
    )
    card_data = ModelCardData(**metadata)
    parity_table = _parity_table(parity_metrics)
    card = ModelCard.from_template(
        card_data,
        model_id=model_id,
        model_summary=(
            model_summary
            or f"{model_name} is a converted checkpoint for layout generation."
        ),
        model_description=model_details,
        developers=developers or "See the original implementation and citation.",
        funded_by=(
            "Funding for the original checkpoint is not separately reported in "
            "this converted artifact."
        ),
        shared_by="creative-graphic-design",
        model_type=model_type,
        language=(
            "The model does not process natural language inputs; metadata uses "
            "English for this model card and category label names."
        ),
        license=license,
        base_model=(
            base_model
            or "Not applicable. This is a conversion of the original checkpoint, "
            "not a fine-tuned derivative of a Hub model."
        ),
        repo=original_implementation_url,
        paper=paper or "See the citation and original implementation.",
        demo="No hosted demo is packaged with this checkpoint.",
        direct_use=intended_uses,
        downstream_use=(
            "Use the generated normalized boxes, labels, and masks as layout "
            "priors for design tooling, document analysis research, or "
            "controlled rendering pipelines that perform their own validation."
        ),
        out_of_scope_use=(
            "Do not use this checkpoint as an OCR model, image renderer, "
            "semantic document understanding model, accessibility verifier, or "
            "unreviewed production UI generator. The model predicts layout "
            "structure only and can produce implausible or overlapping boxes."
        ),
        bias_risks_limitations=limitations,
        bias_recommendations=(
            "Inspect generated layouts before downstream use, validate boxes "
            "against application constraints, and evaluate separately for each "
            "target dataset or design domain."
        ),
        get_started_code=f"```python\n{how_to_use.strip()}\n```",
        training_data=training_data,
        preprocessing=(
            preprocessing
            or "Package adapters convert upstream layout representations to "
            "normalized center `xywh` boxes, dataset-local labels, and mask-based "
            "padding at the public API boundary."
        ),
        training_regime=(
            training_regime
            or "Original upstream training regime; this package converts released "
            "artifacts and does not retrain them."
        ),
        speeds_sizes_times=(
            "Training speed, elapsed time, and hardware are not included in "
            "the upstream checkpoint bundle used for conversion."
        ),
        testing_data=(
            testing_data
            or "Reference parity tests use local outputs from the original "
            "implementation for each converted dataset or checkpoint."
        ),
        testing_factors=(
            "Parity is checked separately for each dataset conversion so that "
            "dataset-specific tokenization and checkpoint weights are covered."
        ),
        testing_metrics=(
            testing_metrics
            or "Recorded parity metrics report exact-match counts or numeric "
            "maximum absolute and relative errors against the original "
            "implementation."
        ),
        results=parity_table,
        results_summary=(
            results_summary
            or "Recorded parity results are listed in the table above; see the "
            "package README for commands that regenerate local references."
        ),
        model_examination=(
            "No separate interpretability study is packaged with this converted "
            "checkpoint."
        ),
        hardware_type=(
            "Original training hardware is not reported in this converted "
            "artifact. Reference regeneration is documented for "
            "`CUDA_VISIBLE_DEVICES=0` when a CUDA device is available."
        ),
        hours_used=(
            "Original training hours are not reported in this converted artifact."
        ),
        cloud_provider=(
            "Original training cloud provider is not reported in this converted "
            "artifact."
        ),
        cloud_region=(
            "Original training compute region is not reported in this converted "
            "artifact."
        ),
        co2_emitted=(
            "Carbon emissions cannot be estimated from the released checkpoint "
            "bundle alone."
        ),
        model_specs=(
            model_specs
            or "The converted package exposes the model, preprocessing, and "
            "pipeline or agent components needed to reproduce local inference."
        ),
        compute_infrastructure=(
            compute_infrastructure
            or "Conversion and parity generation run locally through the `uv` "
            "workspace commands documented in the package README."
        ),
        hardware_requirements=(
            hardware_requirements
            or "CPU is sufficient for package loading and lightweight smoke tests. "
            "CUDA may be required for heavyweight reference parity depending on "
            "the original implementation."
        ),
        software=(
            software
            or "Python 3.11+, the package workspace dependencies, and any optional "
            "original-code dependencies documented by the package."
        ),
        citation_bibtex=f"```bibtex\n{citation_bibtex.strip()}\n```",
        citation_apa=citation_apa or "See the BibTeX citation above.",
        glossary=(
            "`xywh` means normalized center-x, center-y, width, and height. "
            "`Tokenizer exact` counts matching encoded and decoded token "
            "positions. `Logits max abs` and `logits max rel` are maximum "
            "differences against the original denoiser outputs."
        ),
        more_information=(
            "See the package README for copy-paste reproduction commands, "
            "checkpoint conversion, and reference fixture generation."
        ),
        model_card_authors="creative-graphic-design maintainers.",
        model_card_contact=(
            "Open an issue or pull request in the creative-graphic-design "
            "design-generators repository."
        ),
    )
    return card

layoutdm_model_card

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

Build the LayoutDM model card for a converted checkpoint.

Parameters:

Name Type Description Default
dataset DatasetName | str

LayoutDM dataset name, either "rico25" or "publaynet".

required
parity_metrics Sequence[ParityMetricInput] | None

Optional parity rows. Defaults to the checked conversion metrics used by this package.

None

Returns:

Type Description
ModelCard

Validated model card for the requested LayoutDM checkpoint.

Raises:

Type Description
ValueError

If dataset is unsupported.

Examples:

>>> card = layoutdm_model_card(dataset="publaynet")
>>> card.data.to_dict()["datasets"]
['creative-graphic-design/PubLayNet']
Source code in lib/laygen/src/laygen/common/model_card.py
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def layoutdm_model_card(
    *,
    dataset: DatasetName | str,
    parity_metrics: Sequence[ParityMetricInput] | None = None,
) -> ModelCard:
    """Build the LayoutDM model card for a converted checkpoint.

    Args:
        dataset: LayoutDM dataset name, either ``"rico25"`` or ``"publaynet"``.
        parity_metrics: Optional parity rows. Defaults to the checked conversion
            metrics used by this package.

    Returns:
        Validated model card for the requested LayoutDM checkpoint.

    Raises:
        ValueError: If ``dataset`` is unsupported.

    Examples:
        >>> card = layoutdm_model_card(dataset="publaynet")
        >>> card.data.to_dict()["datasets"]
        ['creative-graphic-design/PubLayNet']
    """
    dataset_id = _layoutdm_dataset_id(dataset)
    dataset_config = _layoutdm_dataset_config(dataset)
    model_id = f"creative-graphic-design/layoutdm-{dataset}"
    model_name = f"LayoutDM {dataset}"
    metrics = parity_metrics or [
        ParityMetric(
            dataset=dataset,
            tokenizer_exact="125/125",
            deterministic_exact="125/125",
            logits_max_abs=0.0,
            logits_max_rel=0.0,
        )
    ]
    how_to_use = f"""
from layout_dm import LayoutDMPipeline

path = ".cache/layout-dm/converted/layoutdm-{dataset}"
# After Hub publication: from_pretrained("{model_id}")
pipe = LayoutDMPipeline.from_pretrained(path)
out = pipe(batch_size=1, seed=0, sampling="deterministic")
print(out.bbox, out.labels, out.mask)
"""
    return build_layout_model_card(
        model_id=model_id,
        model_name=model_name,
        dataset_ids=[dataset_id],
        license="apache-2.0",
        library_name="diffusers",
        pipeline_tag="other",
        tags=[
            "layout-generation",
            "layout-dm",
            "diffusers",
            dataset,
        ],
        model_details=(
            "Diffusers-format conversion of the LayoutDM checkpoint for "
            f"`{dataset}`. The pipeline generates normalized center `xywh` layout "
            "boxes, category labels, and masks."
        ),
        intended_uses=(
            "Use this checkpoint for research and evaluation of document and UI "
            "layout generation workflows."
        ),
        limitations=(
            "The converted checkpoint follows the original LayoutDM release and is "
            "intended for layout synthesis, not for image rendering or OCR."
        ),
        how_to_use=how_to_use,
        training_data=(
            f"The original checkpoint was trained on `{dataset_id}`"
            f"{dataset_config} as released by "
            "the original LayoutDM project."
        ),
        parity_metrics=metrics,
        citation_bibtex=_LAYOUTDM_BIBTEX,
        original_implementation_url=("https://github.com/CyberAgentAILab/layout-dm"),
        model_summary=(
            f"{model_name} is a Diffusers-format LayoutDM checkpoint for "
            "conditional-free layout generation."
        ),
        developers="CyberAgentAILab released the original LayoutDM implementation.",
        model_type="Discrete diffusion model for layout generation.",
        base_model=(
            "Not applicable. This is a direct conversion of the original "
            "LayoutDM checkpoint, not a fine-tuned derivative of a Hub model."
        ),
        paper="https://arxiv.org/abs/2303.08137",
        preprocessing=(
            "The converted tokenizer represents each layout element as "
            "discrete category and bounding-box tokens. Bounding boxes use "
            "normalized center `xywh` coordinates and dataset-specific cluster "
            "centers stored with the tokenizer files."
        ),
        training_regime=(
            "Original LayoutDM training regime as released by the upstream "
            "project; this package converts the checkpoint and does not "
            "retrain it."
        ),
        testing_data=(
            "Reference parity tests use deterministic samples and forward-pass "
            "golden tensors generated from the original LayoutDM implementation "
            "for each converted dataset."
        ),
        testing_metrics=(
            "Tokenizer exact-match count, deterministic token-sequence "
            "exact-match count, and denoiser logits maximum absolute and "
            "relative error versus the original implementation."
        ),
        results_summary=(
            "The converted checkpoint matches the generated reference "
            "tensors exactly for tokenizer IO and deterministic sampling; "
            "denoiser logits are within the reported numeric tolerance."
        ),
        model_specs=(
            "LayoutDM models layout generation as discrete diffusion over "
            "category and bounding-box token sequences. This package exposes "
            "the denoiser, tokenizer, scheduler, and Diffusers pipeline needed "
            "to reproduce converted inference."
        ),
        compute_infrastructure=(
            "Conversion and parity generation run locally through the `uv` "
            "workspace commands documented in `models/layout-dm/README.md`."
        ),
        hardware_requirements=(
            "CPU is sufficient for package loading and conversion. CUDA is "
            "recommended for regenerating reference parity outputs and running "
            "the full parity test suite."
        ),
        software=(
            "Python 3.11+, PyTorch, Diffusers, Transformers, and the optional "
            "LayoutDM original-code dependencies declared by the `layout-dm` package."
        ),
        citation_apa=(
            "Inoue, N., Kikuchi, K., Simo-Serra, E., Otani, M., & Yamaguchi, K. "
            "(2023). LayoutDM: Discrete Diffusion Model for Controllable Layout "
            "Generation. CVPR."
        ),
    )

serialization

Serialization helpers for shared layout-generation metadata.

DataclassInstance

Bases: Protocol

Dataclass instance accepted by dataclasses.asdict.

Source code in lib/laygen/src/laygen/common/serialization.py
23
24
25
26
class DataclassInstance(Protocol):
    """Dataclass instance accepted by ``dataclasses.asdict``."""

    __dataclass_fields__: ClassVar[Mapping[str, Field["YamlInputValue"]]]

sanitize_for_yaml

sanitize_for_yaml(value: YamlInputValue) -> YamlValue

Convert enum-rich metadata into objects accepted by yaml.safe_dump.

Parameters:

Name Type Description Default
value YamlInputValue

Metadata value that may contain Enum instances, mappings, sequences, or dataclass instances.

required

Returns:

Type Description
YamlValue

A recursively sanitized value containing only YAML-safe scalar and

YamlValue

container types.

Examples:

>>> from laygen.common import DatasetName
>>> sanitize_for_yaml({"dataset": DatasetName.rico25})
{'dataset': 'rico25'}
Source code in lib/laygen/src/laygen/common/serialization.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def sanitize_for_yaml(value: YamlInputValue) -> YamlValue:
    """Convert enum-rich metadata into objects accepted by ``yaml.safe_dump``.

    Args:
        value: Metadata value that may contain ``Enum`` instances, mappings,
            sequences, or dataclass instances.

    Returns:
        A recursively sanitized value containing only YAML-safe scalar and
        container types.

    Examples:
        >>> from laygen.common import DatasetName
        >>> sanitize_for_yaml({"dataset": DatasetName.rico25})
        {'dataset': 'rico25'}
    """
    if isinstance(value, Enum):
        return str(value.value)
    if is_dataclass(value) and not isinstance(value, type):
        return sanitize_for_yaml(cast(YamlInputValue, asdict(value)))
    if isinstance(value, bytearray):
        return bytes(value)
    if isinstance(value, Mapping):
        return cast(
            YamlValue,
            {
                sanitize_for_yaml(cast(YamlInputValue, key)): sanitize_for_yaml(
                    cast(YamlInputValue, item)
                )
                for key, item in value.items()
            },
        )
    if isinstance(value, Sequence) and not isinstance(value, str | bytes | bytearray):
        return [sanitize_for_yaml(cast(YamlInputValue, item)) for item in value]
    return cast(YamlScalar, value)

testing

Schema assertions and fixtures shared by layout-generation package tests.

ConfigAttributes

Bases: Protocol

Attribute-backed config accepted by parity test helpers.

Source code in lib/laygen/src/laygen/common/testing.py
24
25
26
27
28
29
class ConfigAttributes(Protocol):
    """Attribute-backed config accepted by parity test helpers."""

    def __getattribute__(self, name: str, /) -> ConfigValue:
        """Return a constructor-compatible config field."""
        ...
__getattribute__
__getattribute__(name: str) -> ConfigValue

Return a constructor-compatible config field.

Source code in lib/laygen/src/laygen/common/testing.py
27
28
29
def __getattribute__(self, name: str, /) -> ConfigValue:
    """Return a constructor-compatible config field."""
    ...

LayoutOutputLike

Bases: Protocol

Duck-typed layout output protocol used by shared test helpers.

Source code in lib/laygen/src/laygen/common/testing.py
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
class LayoutOutputLike(Protocol):
    """Duck-typed layout output protocol used by shared test helpers."""

    @property
    def bbox(
        self,
    ) -> (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    ):
        """Layout boxes shaped ``(batch, elements, 4)``."""
        ...

    @property
    def labels(
        self,
    ) -> Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]:
        """Layout labels shaped ``(batch, elements)``."""
        ...

    @property
    def mask(
        self,
    ) -> Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]:
        """Valid-element mask shaped ``(batch, elements)``."""
        ...

    @property
    def id2label(self) -> dict[int, str]:
        """Dataset-local label names keyed by public label id."""
        ...
bbox property
bbox: (
    Float[ndarray, "batch elements 4"]
    | Float[Tensor, "batch elements 4"]
)

Layout boxes shaped (batch, elements, 4).

labels property
labels: (
    Int[ndarray, "batch elements"]
    | Int[Tensor, "batch elements"]
)

Layout labels shaped (batch, elements).

mask property
mask: (
    Bool[ndarray, "batch elements"]
    | Bool[Tensor, "batch elements"]
)

Valid-element mask shaped (batch, elements).

id2label property
id2label: dict[int, str]

Dataset-local label names keyed by public label id.

parity_require_enabled

parity_require_enabled() -> bool

Return whether parity skips should fail.

Returns:

Type Description
bool

True when PARITY_REQUIRE is exactly "1"; otherwise

bool

False.

Examples:

>>> parity_require_enabled() in {True, False}
True
Source code in lib/laygen/src/laygen/common/testing.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def parity_require_enabled() -> bool:
    """Return whether parity skips should fail.

    Returns:
        ``True`` when ``PARITY_REQUIRE`` is exactly ``"1"``; otherwise
        ``False``.

    Raises:
        None.

    Examples:
        >>> parity_require_enabled() in {True, False}
        True
    """
    return os.environ.get("PARITY_REQUIRE") == "1"

skip_or_fail_vendor_parity

skip_or_fail_vendor_parity(
    reason: str,
    *,
    missing_paths: Sequence[str | PathLike[str]] = (),
    regeneration_hint: str | None = None,
) -> NoReturn

Skip or fail a parity test when required assets are absent.

Parameters:

Name Type Description Default
reason str

Human-readable reason the parity assertion cannot run.

required
missing_paths Sequence[str | PathLike[str]]

Optional paths, cache entries, or environment-backed assets that were expected but absent.

()
regeneration_hint str | None

Optional command or instruction for regenerating the missing assets.

None

Returns:

Type Description
NoReturn

This helper never returns. It raises pytest's skip outcome when

NoReturn

PARITY_REQUIRE is unset, and pytest's failure outcome when

NoReturn

PARITY_REQUIRE=1.

Raises:

Type Description
Exception

When PARITY_REQUIRE is not set to "1".

Exception

When PARITY_REQUIRE is set to "1".

Examples:

>>> callable(skip_or_fail_vendor_parity)
True
Source code in lib/laygen/src/laygen/common/testing.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def skip_or_fail_vendor_parity(
    reason: str,
    *,
    missing_paths: Sequence[str | PathLike[str]] = (),
    regeneration_hint: str | None = None,
) -> NoReturn:
    """Skip or fail a parity test when required assets are absent.

    Args:
        reason: Human-readable reason the parity assertion cannot run.
        missing_paths: Optional paths, cache entries, or environment-backed
            assets that were expected but absent.
        regeneration_hint: Optional command or instruction for regenerating the
            missing assets.

    Returns:
        This helper never returns. It raises pytest's skip outcome when
        ``PARITY_REQUIRE`` is unset, and pytest's failure outcome when
        ``PARITY_REQUIRE=1``.

    Raises:
        pytest.skip.Exception: When ``PARITY_REQUIRE`` is not set to ``"1"``.
        pytest.fail.Exception: When ``PARITY_REQUIRE`` is set to ``"1"``.

    Examples:
        >>> callable(skip_or_fail_vendor_parity)
        True
    """
    import pytest

    lines = [reason]
    if missing_paths:
        lines.append("Missing assets:")
        lines.extend(f"- {path}" for path in missing_paths)
    if regeneration_hint:
        lines.append(f"Regeneration hint: {regeneration_hint}")
    message = "\n".join(lines)
    if parity_require_enabled():
        pytest.fail(message, pytrace=False)
    pytest.skip(message)

assert_mask_valid

assert_mask_valid(
    mask: Bool[ndarray, "batch elements"]
    | Bool[Tensor, "batch elements"],
) -> None

Assert that a valid-element mask has the public mask schema.

Source code in lib/laygen/src/laygen/common/testing.py
123
124
125
126
127
128
def assert_mask_valid(
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"],
) -> None:
    """Assert that a valid-element mask has the public mask schema."""
    assert str(getattr(mask, "dtype")) in {"bool", "torch.bool"}
    assert getattr(mask, "ndim") == 2

assert_normalized_xywh

assert_normalized_xywh(
    bbox: Float[ndarray, "batch elements 4"]
    | Float[Tensor, "batch elements 4"],
    mask: Bool[ndarray, "batch elements"]
    | Bool[Tensor, "batch elements"]
    | None = None,
) -> None

Assert that boxes are normalized center xywh tensors.

Source code in lib/laygen/src/laygen/common/testing.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def assert_normalized_xywh(
    bbox: Float[np.ndarray, "batch elements 4"]
    | Float[torch.Tensor, "batch elements 4"],
    mask: Bool[np.ndarray, "batch elements"]
    | Bool[torch.Tensor, "batch elements"]
    | None = None,
) -> None:
    """Assert that boxes are normalized center ``xywh`` tensors."""
    if isinstance(bbox, np.ndarray):
        assert np.issubdtype(bbox.dtype, np.floating)
    else:
        torch_bbox = cast("torch.Tensor", bbox)
        assert torch_bbox.dtype.is_floating_point
    assert getattr(bbox, "shape")[-1] == 4
    values = bbox if mask is None else bbox[mask]
    value_count = values.size if isinstance(values, np.ndarray) else values.numel()
    if value_count:
        if isinstance(values, np.ndarray):
            assert np.all(values >= 0.0)
            assert np.all(values <= 1.0)
        else:
            torch_values = cast("torch.Tensor", values)
            assert torch_values.ge(0.0).all()
            assert torch_values.le(1.0).all()

assert_layout_output_schema

assert_layout_output_schema(
    output: LayoutOutputLike,
    *,
    batch_size: int | None = None,
) -> None

Assert the shared layout output schema.

Parameters:

Name Type Description Default
output LayoutOutputLike

Object with bbox, labels, mask, and id2label attributes.

required
batch_size int | None

Optional expected batch size.

None

Raises:

Type Description
AssertionError

If the object does not satisfy the shared schema.

Source code in lib/laygen/src/laygen/common/testing.py
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
def assert_layout_output_schema(
    output: LayoutOutputLike, *, batch_size: int | None = None
) -> None:
    """Assert the shared layout output schema.

    Args:
        output: Object with ``bbox``, ``labels``, ``mask``, and ``id2label``
            attributes.
        batch_size: Optional expected batch size.

    Raises:
        AssertionError: If the object does not satisfy the shared schema.
    """
    bbox = output.bbox
    labels = output.labels
    mask = output.mask
    assert getattr(bbox, "ndim") == 3 and getattr(bbox, "shape")[-1] == 4
    assert (
        getattr(labels, "shape") == getattr(mask, "shape") == getattr(bbox, "shape")[:2]
    )
    assert str(getattr(labels, "dtype")) in {"int64", "torch.int64"}
    assert_mask_valid(mask)
    assert_normalized_xywh(bbox, mask)
    assert isinstance(output.id2label, dict)
    if batch_size is not None:
        assert getattr(bbox, "shape")[0] == batch_size

assert_generator_reproducible

assert_generator_reproducible(
    callable_: Callable[..., LayoutOutputLike],
) -> None

Assert that a callable is reproducible with identical torch generators.

Source code in lib/laygen/src/laygen/common/testing.py
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def assert_generator_reproducible(
    callable_: Callable[..., LayoutOutputLike],
) -> None:
    """Assert that a callable is reproducible with identical torch generators."""
    import torch

    g1 = torch.Generator().manual_seed(0)
    g2 = torch.Generator().manual_seed(0)
    out1 = callable_(generator=g1)
    out2 = callable_(generator=g2)
    assert torch.equal(
        cast("torch.Tensor", out1.labels), cast("torch.Tensor", out2.labels)
    )
    assert torch.equal(cast("torch.Tensor", out1.mask), cast("torch.Tensor", out2.mask))
    assert torch.allclose(
        cast("torch.Tensor", out1.bbox), cast("torch.Tensor", out2.bbox)
    )

load_torch_checkpoint_state_dict

load_torch_checkpoint_state_dict(
    checkpoint: str | PathLike[str],
    *,
    state_dict_key: str | None = None,
    map_location: str
    | dict[str, str]
    | "torch.device"
    | None = None,
    weights_only: bool | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]

Load a PyTorch checkpoint and return its model state dictionary.

Parameters:

Name Type Description Default
checkpoint str | PathLike[str]

Checkpoint path passed to :func:torch.load.

required
state_dict_key str | None

Optional key used by Lightning-style checkpoints.

None
map_location str | dict[str, str] | 'torch.device' | None

Device mapping passed to :func:torch.load.

None
weights_only bool | None

Optional torch.load safety flag. None preserves PyTorch's default for compatibility with older checkpoints.

None

Returns:

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

Mapping of checkpoint parameter names to tensors.

Source code in lib/laygen/src/laygen/common/testing.py
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
def load_torch_checkpoint_state_dict(
    checkpoint: str | PathLike[str],
    *,
    state_dict_key: str | None = None,
    map_location: str | dict[str, str] | "torch.device" | None = None,
    weights_only: bool | None = None,
) -> dict[str, Shaped[torch.Tensor, "..."]]:
    """Load a PyTorch checkpoint and return its model state dictionary.

    Args:
        checkpoint: Checkpoint path passed to :func:`torch.load`.
        state_dict_key: Optional key used by Lightning-style checkpoints.
        map_location: Device mapping passed to :func:`torch.load`.
        weights_only: Optional ``torch.load`` safety flag. ``None`` preserves
            PyTorch's default for compatibility with older checkpoints.

    Returns:
        Mapping of checkpoint parameter names to tensors.
    """
    import torch

    if weights_only is None:
        checkpoint_data = torch.load(checkpoint, map_location=map_location)
    else:
        checkpoint_data = torch.load(
            checkpoint,
            map_location=map_location,
            weights_only=weights_only,
        )
    if state_dict_key is not None:
        checkpoint_data = checkpoint_data[state_dict_key]
    return cast('dict[str, Shaped[torch.Tensor, "..."]]', checkpoint_data)

strip_torch_state_dict_prefix

strip_torch_state_dict_prefix(
    state_dict: Mapping[str, Shaped[Tensor, "..."]],
    *,
    strip_prefix: str,
    include_prefix: str | None = None,
) -> OrderedDict[str, Shaped[torch.Tensor, "..."]]

Return a state dict with a wrapper prefix removed.

Parameters:

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

Source state dictionary.

required
strip_prefix str

Prefix to remove from each emitted key.

required
include_prefix str | None

Optional prefix filter. When set, only matching keys are emitted.

None

Returns:

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

Ordered state dictionary with normalized keys.

Source code in lib/laygen/src/laygen/common/testing.py
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
def strip_torch_state_dict_prefix(
    state_dict: Mapping[str, Shaped[torch.Tensor, "..."]],
    *,
    strip_prefix: str,
    include_prefix: str | None = None,
) -> OrderedDict[str, Shaped[torch.Tensor, "..."]]:
    """Return a state dict with a wrapper prefix removed.

    Args:
        state_dict: Source state dictionary.
        strip_prefix: Prefix to remove from each emitted key.
        include_prefix: Optional prefix filter. When set, only matching keys are
            emitted.

    Returns:
        Ordered state dictionary with normalized keys.
    """
    return OrderedDict(
        (key.removeprefix(strip_prefix), value)
        for key, value in state_dict.items()
        if include_prefix is None or key.startswith(include_prefix)
    )

vendor_backbone_kwargs

vendor_backbone_kwargs(
    config: Mapping[str, ConfigValue] | ConfigAttributes,
    fields: Sequence[str],
    *,
    aliases: Mapping[str, str] | None = None,
    overrides: Mapping[str, ConfigValue] | None = None,
) -> dict[str, ConfigValue]

Build checkpoint-backbone constructor kwargs from a config object.

Parameters:

Name Type Description Default
config Mapping[str, ConfigValue] | ConfigAttributes

Object or mapping that stores canonical package configuration.

required
fields Sequence[str]

Constructor argument names to read in order.

required
aliases Mapping[str, str] | None

Optional mapping from constructor argument name to config field name.

None
overrides Mapping[str, ConfigValue] | None

Optional explicit values that take precedence over config.

None

Returns:

Type Description
dict[str, ConfigValue]

Ordered keyword arguments suitable for a checkpoint-backbone constructor.

Source code in lib/laygen/src/laygen/common/testing.py
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
def vendor_backbone_kwargs(
    config: Mapping[str, ConfigValue] | ConfigAttributes,
    fields: Sequence[str],
    *,
    aliases: Mapping[str, str] | None = None,
    overrides: Mapping[str, ConfigValue] | None = None,
) -> dict[str, ConfigValue]:
    """Build checkpoint-backbone constructor kwargs from a config object.

    Args:
        config: Object or mapping that stores canonical package configuration.
        fields: Constructor argument names to read in order.
        aliases: Optional mapping from constructor argument name to config field name.
        overrides: Optional explicit values that take precedence over ``config``.

    Returns:
        Ordered keyword arguments suitable for a checkpoint-backbone constructor.
    """
    aliases = aliases or {}
    overrides = overrides or {}
    kwargs: dict[str, ConfigValue] = {}
    for field in fields:
        if field in overrides:
            kwargs[field] = overrides[field]
            continue
        source_field = aliases.get(field, field)
        if isinstance(config, Mapping):
            kwargs[field] = cast("Mapping[str, ConfigValue]", config)[source_field]
        else:
            kwargs[field] = getattr(config, source_field)
    return kwargs

install_jaxtyping_runtime_hook

install_jaxtyping_runtime_hook(
    modules: Sequence[str],
) -> AbstractContextManager[None]

Install the test-only jaxtyping runtime checker for target modules.

Parameters:

Name Type Description Default
modules Sequence[str]

Importable module or package names to hook before import.

required

Returns:

Type Description
AbstractContextManager[None]

Context manager returned by :func:jaxtyping.install_import_hook.

Examples:

>>> hook = install_jaxtyping_runtime_hook(["laygen.common.bbox"])
>>> hasattr(hook, "__enter__")
True
Source code in lib/laygen/src/laygen/common/testing.py
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
def install_jaxtyping_runtime_hook(
    modules: Sequence[str],
) -> AbstractContextManager[None]:
    """Install the test-only jaxtyping runtime checker for target modules.

    Args:
        modules: Importable module or package names to hook before import.

    Returns:
        Context manager returned by :func:`jaxtyping.install_import_hook`.

    Examples:
        >>> hook = install_jaxtyping_runtime_hook(["laygen.common.bbox"])
        >>> hasattr(hook, "__enter__")
        True
    """
    from jaxtyping import install_import_hook

    return install_import_hook(modules, "beartype.beartype")

tokenization

Shared helpers for lightweight whitespace layout tokenizers.

WhitespaceTokenizerMixin

Mixin for tokenizers backed by tokenizer-local id dictionaries.

Source code in lib/laygen/src/laygen/common/tokenization.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
class WhitespaceTokenizerMixin:
    """Mixin for tokenizers backed by tokenizer-local id dictionaries."""

    _token2id: dict[str, int]
    _id2token: dict[int, str]
    unk_token_id: int
    unk_token: str

    @property
    def vocab_size(self) -> int:
        """Return the number of vocabulary entries."""
        return len(self._token2id)

    def get_vocab(self) -> dict[str, int]:
        """Return token-to-id mapping."""
        return dict(self._token2id)

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

    def _convert_token_to_id(self, token: str) -> int:
        return convert_token_to_id(self._token2id, token, self.unk_token_id)

    def _convert_id_to_token(self, index: int) -> str:
        return convert_id_to_token(self._id2token, index, self.unk_token)

    def convert_tokens_to_string(self, tokens: list[str]) -> str:
        """Join layout tokens with spaces."""
        return join_tokens(tokens)
vocab_size property
vocab_size: int

Return the number of vocabulary entries.

get_vocab
get_vocab() -> dict[str, int]

Return token-to-id mapping.

Source code in lib/laygen/src/laygen/common/tokenization.py
86
87
88
def get_vocab(self) -> dict[str, int]:
    """Return token-to-id mapping."""
    return dict(self._token2id)
convert_tokens_to_string
convert_tokens_to_string(tokens: list[str]) -> str

Join layout tokens with spaces.

Source code in lib/laygen/src/laygen/common/tokenization.py
102
103
104
def convert_tokens_to_string(self, tokens: list[str]) -> str:
    """Join layout tokens with spaces."""
    return join_tokens(tokens)

build_token_maps

build_token_maps(
    *,
    vocab_file: str | PathLike[str] | None,
    tokens: Sequence[str] | None,
    base_tokens: Sequence[str],
    numeric_id_vocab: bool = False,
) -> tuple[dict[str, int], dict[int, str]]

Build token/id maps from a JSON vocabulary file or synthetic token list.

Source code in lib/laygen/src/laygen/common/tokenization.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def build_token_maps(
    *,
    vocab_file: str | PathLike[str] | None,
    tokens: Sequence[str] | None,
    base_tokens: Sequence[str],
    numeric_id_vocab: bool = False,
) -> tuple[dict[str, int], dict[int, str]]:
    """Build token/id maps from a JSON vocabulary file or synthetic token list."""
    if vocab_file is not None:
        with Path(vocab_file).open() as f:
            raw_vocab = cast(dict[str, str | int], json.load(f))
        if numeric_id_vocab and all(str(key).isdigit() for key in raw_vocab):
            id2token = {int(key): str(value) for key, value in raw_vocab.items()}
            return {value: key for key, value in id2token.items()}, id2token
        token2id = {str(key): int(str(value)) for key, value in raw_vocab.items()}
        return token2id, {value: key for key, value in token2id.items()}

    token2id = {token: idx for idx, token in enumerate(base_tokens)}
    for token in tokens or ():
        if token not in token2id:
            token2id[token] = len(token2id)
    return token2id, {idx: token for token, idx in token2id.items()}

split_whitespace_tokens

split_whitespace_tokens(text: str) -> list[str]

Split a layout token string on whitespace.

Source code in lib/laygen/src/laygen/common/tokenization.py
36
37
38
def split_whitespace_tokens(text: str) -> list[str]:
    """Split a layout token string on whitespace."""
    return text.strip().split()

convert_token_to_id

convert_token_to_id(
    token2id: dict[str, int], token: str, unk_token_id: int
) -> int

Convert a token to an id using a tokenizer-local unknown-token id.

Source code in lib/laygen/src/laygen/common/tokenization.py
41
42
43
def convert_token_to_id(token2id: dict[str, int], token: str, unk_token_id: int) -> int:
    """Convert a token to an id using a tokenizer-local unknown-token id."""
    return token2id.get(token, unk_token_id)

convert_id_to_token

convert_id_to_token(
    id2token: dict[int, str], index: int, unk_token: str
) -> str

Convert an id to a token using a tokenizer-local unknown token string.

Source code in lib/laygen/src/laygen/common/tokenization.py
46
47
48
def convert_id_to_token(id2token: dict[int, str], index: int, unk_token: str) -> str:
    """Convert an id to a token using a tokenizer-local unknown token string."""
    return id2token.get(int(index), unk_token)

join_tokens

join_tokens(tokens: Sequence[str]) -> str

Join already-tokenized layout tokens with spaces.

Source code in lib/laygen/src/laygen/common/tokenization.py
51
52
53
def join_tokens(tokens: Sequence[str]) -> str:
    """Join already-tokenized layout tokens with spaces."""
    return " ".join(tokens)

save_json_vocabulary

save_json_vocabulary(
    *,
    save_directory: str | PathLike[str],
    filename: str,
    data: dict[str, int] | dict[str, str],
    filename_prefix: str | None = None,
) -> tuple[str]

Save tokenizer vocabulary JSON and return the generated path.

Source code in lib/laygen/src/laygen/common/tokenization.py
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
def save_json_vocabulary(
    *,
    save_directory: str | PathLike[str],
    filename: str,
    data: dict[str, int] | dict[str, str],
    filename_prefix: str | None = None,
) -> tuple[str]:
    """Save tokenizer vocabulary JSON and return the generated path."""
    out_dir = Path(save_directory)
    out_dir.mkdir(parents=True, exist_ok=True)
    name = filename if filename_prefix is None else f"{filename_prefix}-{filename}"
    out_path = out_dir / name
    with out_path.open("w") as f:
        json.dump(data, f, indent=2, sort_keys=True)
    return (str(out_path),)

training

Shared training-step helpers for layout generator Lightning modules.

ScalarLogger

Bases: Protocol

Minimal scalar logging protocol implemented by Lightning modules.

Source code in lib/laygen/src/laygen/common/training.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class ScalarLogger(Protocol):
    """Minimal scalar logging protocol implemented by Lightning modules."""

    def log(
        self,
        name: str,
        value: Float[torch.Tensor, ""],
        *,
        prog_bar: bool = False,
        on_step: bool | None = None,
        on_epoch: bool | None = None,
        batch_size: int | None = None,
    ) -> None:
        """Log a scalar training metric.

        Args:
            name: Metric name.
            value: Scalar tensor value.
            prog_bar: Whether to show the value in the progress bar.
            on_step: Whether to aggregate the value per step.
            on_epoch: Whether to aggregate the value per epoch.
            batch_size: Batch size used by Lightning metric aggregation.
        """
        _ = (name, value, prog_bar, on_step, on_epoch, batch_size)
log
log(
    name: str,
    value: Float[Tensor, ""],
    *,
    prog_bar: bool = False,
    on_step: bool | None = None,
    on_epoch: bool | None = None,
    batch_size: int | None = None,
) -> None

Log a scalar training metric.

Parameters:

Name Type Description Default
name str

Metric name.

required
value Float[Tensor, '']

Scalar tensor value.

required
prog_bar bool

Whether to show the value in the progress bar.

False
on_step bool | None

Whether to aggregate the value per step.

None
on_epoch bool | None

Whether to aggregate the value per epoch.

None
batch_size int | None

Batch size used by Lightning metric aggregation.

None
Source code in lib/laygen/src/laygen/common/training.py
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
def log(
    self,
    name: str,
    value: Float[torch.Tensor, ""],
    *,
    prog_bar: bool = False,
    on_step: bool | None = None,
    on_epoch: bool | None = None,
    batch_size: int | None = None,
) -> None:
    """Log a scalar training metric.

    Args:
        name: Metric name.
        value: Scalar tensor value.
        prog_bar: Whether to show the value in the progress bar.
        on_step: Whether to aggregate the value per step.
        on_epoch: Whether to aggregate the value per epoch.
        batch_size: Batch size used by Lightning metric aggregation.
    """
    _ = (name, value, prog_bar, on_step, on_epoch, batch_size)

sum_loss_values

sum_loss_values(
    losses: Mapping[str, Float[Tensor, ""]],
) -> Float[torch.Tensor, ""]

Sum scalar loss values with the canonical training reduction.

Parameters:

Name Type Description Default
losses Mapping[str, Float[Tensor, '']]

Mapping from metric names to scalar loss tensors.

required

Returns:

Type Description
Float[Tensor, '']

Scalar tensor containing the sum of all loss values.

Examples:

>>> import torch
>>> sum_loss_values({"a": torch.tensor(1.0), "b": torch.tensor(2.0)})
tensor(3.)
Source code in lib/laygen/src/laygen/common/training.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
def sum_loss_values(
    losses: Mapping[str, Float[torch.Tensor, ""]],
) -> Float[torch.Tensor, ""]:
    """Sum scalar loss values with the canonical training reduction.

    Args:
        losses: Mapping from metric names to scalar loss tensors.

    Returns:
        Scalar tensor containing the sum of all loss values.

    Examples:
        >>> import torch
        >>> sum_loss_values({"a": torch.tensor(1.0), "b": torch.tensor(2.0)})
        tensor(3.)
    """
    import torch

    return torch.stack(tuple(losses.values())).sum()

log_training_losses

log_training_losses(
    logger: ScalarLogger,
    losses: Mapping[str, Float[Tensor, ""]],
    total: Float[Tensor, ""],
    *,
    batch_size: int = 1,
) -> None

Log per-component and total training losses.

Parameters:

Name Type Description Default
logger ScalarLogger

Object exposing Lightning-compatible log.

required
losses Mapping[str, Float[Tensor, '']]

Per-component scalar loss values.

required
total Float[Tensor, '']

Total scalar training loss.

required
batch_size int

Batch size used by Lightning metric aggregation.

1

Returns:

Type Description
None

None.

Source code in lib/laygen/src/laygen/common/training.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
def log_training_losses(
    logger: ScalarLogger,
    losses: Mapping[str, Float[torch.Tensor, ""]],
    total: Float[torch.Tensor, ""],
    *,
    batch_size: int = 1,
) -> None:
    """Log per-component and total training losses.

    Args:
        logger: Object exposing Lightning-compatible ``log``.
        losses: Per-component scalar loss values.
        total: Total scalar training loss.
        batch_size: Batch size used by Lightning metric aggregation.

    Returns:
        None.
    """
    for key, value in losses.items():
        logger.log(key, value, on_step=True, on_epoch=True, batch_size=batch_size)
    logger.log(
        "train_loss",
        total,
        prog_bar=True,
        on_step=True,
        on_epoch=True,
        batch_size=batch_size,
    )

finish_training_step

finish_training_step(
    logger: ScalarLogger,
    losses: Mapping[str, Float[Tensor, ""]],
    trace: Mapping[str, Shaped[Tensor, "..."]],
    *,
    batch_size: int = 1,
) -> tuple[
    Float[torch.Tensor, ""],
    dict[str, Shaped[torch.Tensor, "..."]],
]

Reduce losses, log training metrics, and append train_loss to a trace.

Parameters:

Name Type Description Default
logger ScalarLogger

Object exposing Lightning-compatible log.

required
losses Mapping[str, Float[Tensor, '']]

Per-component scalar loss values.

required
trace Mapping[str, Shaped[Tensor, '...']]

Training trace entries produced before the optimizer step.

required
batch_size int

Batch size used by Lightning metric aggregation.

1

Returns:

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

Total scalar loss and an updated detached trace mapping.

Source code in lib/laygen/src/laygen/common/training.py
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def finish_training_step(
    logger: ScalarLogger,
    losses: Mapping[str, Float[torch.Tensor, ""]],
    trace: Mapping[str, Shaped[torch.Tensor, "..."]],
    *,
    batch_size: int = 1,
) -> tuple[Float[torch.Tensor, ""], dict[str, Shaped[torch.Tensor, "..."]]]:
    """Reduce losses, log training metrics, and append ``train_loss`` to a trace.

    Args:
        logger: Object exposing Lightning-compatible ``log``.
        losses: Per-component scalar loss values.
        trace: Training trace entries produced before the optimizer step.
        batch_size: Batch size used by Lightning metric aggregation.

    Returns:
        Total scalar loss and an updated detached trace mapping.
    """
    total = sum_loss_values(losses)
    log_training_losses(logger, losses, total, batch_size=batch_size)
    return total, {**trace, "train_loss": total.detach()}

log_validation_loss

log_validation_loss(
    logger: ScalarLogger,
    total: Float[Tensor, ""],
    *,
    batch_size: int = 1,
) -> None

Log the canonical validation loss metric.

Parameters:

Name Type Description Default
logger ScalarLogger

Object exposing Lightning-compatible log.

required
total Float[Tensor, '']

Scalar validation loss.

required
batch_size int

Batch size used by Lightning metric aggregation.

1

Returns:

Type Description
None

None.

Source code in lib/laygen/src/laygen/common/training.py
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
def log_validation_loss(
    logger: ScalarLogger,
    total: Float[torch.Tensor, ""],
    *,
    batch_size: int = 1,
) -> None:
    """Log the canonical validation loss metric.

    Args:
        logger: Object exposing Lightning-compatible ``log``.
        total: Scalar validation loss.
        batch_size: Batch size used by Lightning metric aggregation.

    Returns:
        None.
    """
    logger.log(
        "val_loss",
        total,
        prog_bar=True,
        on_step=False,
        on_epoch=True,
        batch_size=batch_size,
    )

vendor

Vendor repository path helpers shared by parity scripts.

vendor_root

vendor_root(
    repo: str,
    *,
    marker: str | Path | None = None,
    path: str | Path | None = None,
    repo_root: Path | None = None,
    cwd: Path | None = None,
) -> Path

Resolve an initialized vendor submodule checkout.

Parameters:

Name Type Description Default
repo str

Repository directory name under vendor/.

required
marker str | Path | None

Optional file that must exist inside the vendor checkout.

None
path str | Path | None

Optional user-supplied path. Defaults to vendor/<repo>.

None
repo_root Path | None

Optional repository root override for tests.

None
cwd Path | None

Optional current working directory override for tests.

None

Returns:

Type Description
Path

Resolved path to the vendor checkout.

Raises:

Type Description
FileNotFoundError

If the checkout or marker cannot be found.

Examples:

>>> vendor_root("const-layout")
PosixPath('...')
Source code in lib/laygen/src/laygen/common/vendor.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def vendor_root(
    repo: str,
    *,
    marker: str | Path | None = None,
    path: str | Path | None = None,
    repo_root: Path | None = None,
    cwd: Path | None = None,
) -> Path:
    """Resolve an initialized vendor submodule checkout.

    Args:
        repo: Repository directory name under `vendor/`.
        marker: Optional file that must exist inside the vendor checkout.
        path: Optional user-supplied path. Defaults to `vendor/<repo>`.
        repo_root: Optional repository root override for tests.
        cwd: Optional current working directory override for tests.

    Returns:
        Resolved path to the vendor checkout.

    Raises:
        FileNotFoundError: If the checkout or marker cannot be found.

    Examples:
        >>> vendor_root("const-layout")  # doctest: +SKIP
        PosixPath('...')
    """
    repo_dir = Path("vendor") / repo
    requested = Path(path) if path is not None else repo_dir
    project_root = repo_root or Path(__file__).resolve().parents[4]
    current = cwd or Path.cwd()
    marker_path = Path(marker) if marker is not None else None

    candidates = _candidate_roots(requested, repo_dir, project_root, current)
    seen: set[Path] = set()
    initialized_missing: list[Path] = []
    for candidate in candidates:
        resolved = candidate.expanduser().resolve()
        if resolved in seen:
            continue
        seen.add(resolved)
        if marker_path is None:
            if resolved.exists():
                return resolved
        elif (resolved / marker_path).exists():
            return resolved
        elif resolved.exists():
            initialized_missing.append(resolved)

    searched = "\n".join(f"- {path}" for path in seen)
    hint = f"Run `git submodule update --init vendor/{repo}` from the repository root."
    if initialized_missing:
        missing = "\n".join(f"- {path}" for path in initialized_missing)
        raise FileNotFoundError(
            f"Found vendor/{repo}, but required marker `{marker_path}` is missing. "
            f"{hint}\nChecked initialized paths:\n{missing}"
        )

    raise FileNotFoundError(
        f"Could not find initialized vendor/{repo}. {hint}\nSearched:\n{searched}"
    )

visualization

Lightweight visualization helpers for generated layouts.

render_layout

render_layout(
    bbox: Float[Tensor, "elements 4"],
    labels: Int[Tensor, "elements"],
    mask: Bool[Tensor, "elements"],
    id2label: dict[int, str],
    *,
    ax: Axes | None = None,
    canvas_size: tuple[int, int] = (1, 1),
    colors: Iterable[str] | None = None,
) -> Axes

Render one layout on a Matplotlib axis.

Parameters:

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

Normalized center xywh boxes for one sample.

required
labels Int[Tensor, 'elements']

Integer labels for one sample.

required
mask Bool[Tensor, 'elements']

Boolean valid-element mask for one sample.

required
id2label dict[int, str]

Mapping from integer ids to label names.

required
ax Axes | None

Optional Matplotlib axis. A new axis is created when omitted.

None
canvas_size tuple[int, int]

Canvas size as (width, height).

(1, 1)
colors Iterable[str] | None

Optional color cycle.

None

Returns:

Type Description
Axes

Axis containing rectangle patches and label text.

Examples:

>>> import torch
>>> ax = render_layout(
...     torch.zeros(1, 4),
...     torch.zeros(1, dtype=torch.long),
...     torch.ones(1, dtype=torch.bool),
...     {0: "text"},
... )
>>> ax is not None
True
Source code in lib/laygen/src/laygen/common/visualization.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
def render_layout(
    bbox: Float[torch.Tensor, "elements 4"],
    labels: Int[torch.Tensor, "elements"],
    mask: Bool[torch.Tensor, "elements"],
    id2label: dict[int, str],
    *,
    ax: Axes | None = None,
    canvas_size: tuple[int, int] = (1, 1),
    colors: Iterable[str] | None = None,
) -> Axes:
    """Render one layout on a Matplotlib axis.

    Args:
        bbox: Normalized center ``xywh`` boxes for one sample.
        labels: Integer labels for one sample.
        mask: Boolean valid-element mask for one sample.
        id2label: Mapping from integer ids to label names.
        ax: Optional Matplotlib axis. A new axis is created when omitted.
        canvas_size: Canvas size as ``(width, height)``.
        colors: Optional color cycle.

    Returns:
        Axis containing rectangle patches and label text.

    Examples:
        >>> import torch
        >>> ax = render_layout(
        ...     torch.zeros(1, 4),
        ...     torch.zeros(1, dtype=torch.long),
        ...     torch.ones(1, dtype=torch.bool),
        ...     {0: "text"},
        ... )
        >>> ax is not None
        True
    """
    if ax is None:
        _, ax = plt.subplots()
    palette = list(colors or plt.rcParams["axes.prop_cycle"].by_key()["color"])
    width, height = canvas_size
    ax.set_xlim(0, width)
    ax.set_ylim(height, 0)
    ax.set_aspect("equal")
    ltrb = xywh_to_ltrb(bbox.detach().cpu())
    for i, valid in enumerate(mask.detach().cpu().tolist()):
        if not valid:
            continue
        left, top, right, bottom = ltrb[i].tolist()
        color = palette[int(labels[i]) % len(palette)]
        rect = Rectangle(
            (left * width, top * height),
            (right - left) * width,
            (bottom - top) * height,
            fill=False,
            edgecolor=color,
        )
        ax.add_patch(rect)
        ax.text(
            left * width,
            top * height,
            id2label.get(int(labels[i]), str(int(labels[i]))),
            color=color,
            fontsize=8,
        )
    return ax

modeling_outputs

Canonical Transformers-compatible output types for layout generation.

This module is intentionally excluded from jaxtyping runtime import hooks because Transformers ModelOutput dataclasses are backend-neutral containers. Static annotations document the accepted NumPy/torch field shapes, while runtime shape guarantees are provided by laygen.common.testing.assert_layout_output_schema.

LayoutGenerationOutput dataclass

Bases: ModelOutput

Canonical layout-generation output for Transformers-style APIs.

Attributes:

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

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

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

Dataset-local integer labels with shape (batch, elements).

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

Boolean valid-element mask with shape (batch, elements).

id2label dict[int, str]

Mapping from integer label ids to display names.

sequences object | None

Optional raw token sequences.

scores object | None

Optional per-token or per-element scores.

trajectory object | None

Optional sampling trajectory.

intermediates object | None

Optional model-specific debug or auxiliary data.

Examples:

>>> import numpy as np
>>> output = LayoutGenerationOutput(
...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
...     labels=np.zeros((1, 1), dtype=np.int64),
...     mask=np.ones((1, 1), dtype=bool),
...     id2label={0: "text"},
... )
>>> output["bbox"].shape
(1, 1, 4)
Source code in lib/laygen/src/laygen/modeling_outputs.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@dataclass
class LayoutGenerationOutput(ModelOutput):
    """Canonical layout-generation output for Transformers-style APIs.

    Attributes:
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        labels: Dataset-local integer labels with shape ``(batch, elements)``.
        mask: Boolean valid-element mask with shape ``(batch, elements)``.
        id2label: Mapping from integer label ids to display names.
        sequences: Optional raw token sequences.
        scores: Optional per-token or per-element scores.
        trajectory: Optional sampling trajectory.
        intermediates: Optional model-specific debug or auxiliary data.

    Examples:
        >>> import numpy as np
        >>> output = LayoutGenerationOutput(
        ...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
        ...     labels=np.zeros((1, 1), dtype=np.int64),
        ...     mask=np.ones((1, 1), dtype=bool),
        ...     id2label={0: "text"},
        ... )
        >>> output["bbox"].shape
        (1, 1, 4)
    """

    bbox: (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    )
    labels: Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"] = (
        cast(
            'Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]',
            None,
        )
    )
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"] = (
        cast(
            'Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]',
            None,
        )
    )
    id2label: dict[int, str] = cast(dict[int, str], None)
    sequences: object | None = None
    scores: object | None = None
    trajectory: object | None = None
    intermediates: object | None = None

nn

Shared PyTorch neural-network helpers for layout generation models.

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()

ElementPositionalEmbedding

Bases: Module

Learned element and attribute positional embedding.

Origin

This learned element/attribute positional embedding is specific to CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

Parameters:

Name Type Description Default
dim_model int

Embedding dimension.

required
max_token_length int

Maximum flattened token sequence length.

required
n_attr_per_elem int

Number of attributes per layout element.

5
Source code in lib/laygen/src/laygen/nn/embeddings.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class ElementPositionalEmbedding(nn.Module):
    """Learned element and attribute positional embedding.

    Origin:
        This learned element/attribute positional embedding is specific to
        CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

    Args:
        dim_model: Embedding dimension.
        max_token_length: Maximum flattened token sequence length.
        n_attr_per_elem: Number of attributes per layout element.
    """

    def __init__(
        self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
    ) -> None:
        """Initialize element and attribute embedding parameters."""
        super().__init__()
        self.n_elem = max_token_length // n_attr_per_elem
        self.n_attr_per_elem = n_attr_per_elem
        self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
        self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))

    def forward(
        self, h: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Return positional embeddings matching hidden-state length.

        Args:
            h: Hidden states shaped ``(batch, sequence, dim)``.

        Returns:
            Positional embedding tensor shaped like ``h``.
        """
        batch, seq_len = h.shape[:2]
        elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
        attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
        emb = (elem_emb + attr_emb)[:seq_len]
        return repeat(emb, "s d -> b s d", b=batch)

    @property
    def no_decay_param_names(self) -> list[str]:
        """Return parameter names that should skip weight decay."""
        return ["elem_emb", "attr_emb"]

no_decay_param_names property

no_decay_param_names: list[str]

Return parameter names that should skip weight decay.

__init__

__init__(
    dim_model: int,
    max_token_length: int,
    n_attr_per_elem: int = 5,
) -> None

Initialize element and attribute embedding parameters.

Source code in lib/laygen/src/laygen/nn/embeddings.py
115
116
117
118
119
120
121
122
123
def __init__(
    self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
) -> None:
    """Initialize element and attribute embedding parameters."""
    super().__init__()
    self.n_elem = max_token_length // n_attr_per_elem
    self.n_attr_per_elem = n_attr_per_elem
    self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
    self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))

forward

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

Return positional embeddings matching hidden-state length.

Parameters:

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

Hidden states shaped (batch, sequence, dim).

required

Returns:

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

Positional embedding tensor shaped like h.

Source code in lib/laygen/src/laygen/nn/embeddings.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def forward(
    self, h: Float[torch.Tensor, "batch tokens channels"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Return positional embeddings matching hidden-state length.

    Args:
        h: Hidden states shaped ``(batch, sequence, dim)``.

    Returns:
        Positional embedding tensor shaped like ``h``.
    """
    batch, seq_len = h.shape[:2]
    elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
    attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
    emb = (elem_emb + attr_emb)[:seq_len]
    return repeat(emb, "s d -> b s d", b=batch)

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()

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

TimestepTransformerEncoder

Bases: Module

Stack of cloned timestep transformer encoder layers.

Origin

This is the VQ-Diffusion-style cloned TransformerEncoder wrapper used by LayoutDM and Layout-Corrector around the shared Block layer.

Parameters:

Name Type Description Default
encoder_layer TimestepTransformerEncoderLayer

Layer to clone for the stack.

required
num_layers int

Number of cloned layers.

required
norm Module | None

Optional final normalization module.

None
Source code in lib/laygen/src/laygen/nn/blocks.py
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
class TimestepTransformerEncoder(nn.Module):
    """Stack of cloned timestep transformer encoder layers.

    Origin:
        This is the VQ-Diffusion-style cloned ``TransformerEncoder`` wrapper
        used by LayoutDM and Layout-Corrector around the shared ``Block`` layer.

    Args:
        encoder_layer: Layer to clone for the stack.
        num_layers: Number of cloned layers.
        norm: Optional final normalization module.
    """

    def __init__(
        self,
        encoder_layer: TimestepTransformerEncoderLayer,
        num_layers: int,
        norm: nn.Module | None = None,
    ) -> None:
        """Initialize a transformer encoder stack."""
        super().__init__()
        self.layers = clone_module_list(encoder_layer, num_layers)
        self.num_layers = num_layers
        self.norm = norm

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        mask: Bool[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Int[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Run hidden states through all encoder layers."""
        output = src
        for layer in self.layers:
            output = layer(
                output,
                src_mask=mask,
                src_key_padding_mask=src_key_padding_mask,
                timestep=timestep,
            )
        return self.norm(output) if self.norm is not None else output

__init__

__init__(
    encoder_layer: TimestepTransformerEncoderLayer,
    num_layers: int,
    norm: Module | None = None,
) -> None

Initialize a transformer encoder stack.

Source code in lib/laygen/src/laygen/nn/blocks.py
155
156
157
158
159
160
161
162
163
164
165
def __init__(
    self,
    encoder_layer: TimestepTransformerEncoderLayer,
    num_layers: int,
    norm: nn.Module | None = None,
) -> None:
    """Initialize a transformer encoder stack."""
    super().__init__()
    self.layers = clone_module_list(encoder_layer, num_layers)
    self.num_layers = num_layers
    self.norm = norm

forward

forward(
    src: Float[Tensor, "batch tokens channels"],
    mask: Bool[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Int[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Run hidden states through all encoder layers.

Source code in lib/laygen/src/laygen/nn/blocks.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    mask: Bool[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Int[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Run hidden states through all encoder layers."""
    output = src
    for layer in self.layers:
        output = layer(
            output,
            src_mask=mask,
            src_key_padding_mask=src_key_padding_mask,
            timestep=timestep,
        )
    return self.norm(output) if self.norm is not None else output

TimestepTransformerEncoderLayer

Bases: Module

Transformer encoder block with optional adaptive normalization.

Origin

This class follows VQ-Diffusion Block rather than Diffusers BasicTransformerBlock so checkpoint keys and adaptive norm call conventions stay compatible with LayoutDM, LACE, and Layout-Corrector.

Parameters:

Name Type Description Default
d_model int

Hidden dimension.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden dimension.

2048
dropout float

Dropout probability.

0.0
activation ActivationName | str | ActivationFn

Feed-forward activation name or callable.

relu
batch_first bool

Whether inputs use (batch, seq, dim) order.

True
norm_first bool

Whether to use pre-norm residual blocks.

True
diffusion_step int

Maximum diffusion timestep.

100
timestep_type TimestepEmbeddingType | str | None

Timestep-conditioned normalization variant.

adalayernorm
Source code in lib/laygen/src/laygen/nn/blocks.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
class TimestepTransformerEncoderLayer(nn.Module):
    """Transformer encoder block with optional adaptive normalization.

    Origin:
        This class follows VQ-Diffusion ``Block`` rather than Diffusers
        ``BasicTransformerBlock`` so checkpoint keys and adaptive norm call
        conventions stay compatible with LayoutDM, LACE, and Layout-Corrector.

    Args:
        d_model: Hidden dimension.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden dimension.
        dropout: Dropout probability.
        activation: Feed-forward activation name or callable.
        batch_first: Whether inputs use ``(batch, seq, dim)`` order.
        norm_first: Whether to use pre-norm residual blocks.
        diffusion_step: Maximum diffusion timestep.
        timestep_type: Timestep-conditioned normalization variant.
    """

    def __init__(
        self,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        dropout: float = 0.0,
        activation: ActivationName | str | ActivationFn = ActivationName.relu,
        batch_first: bool = True,
        norm_first: bool = True,
        diffusion_step: int = 100,
        timestep_type: TimestepEmbeddingType
        | str
        | None = TimestepEmbeddingType.adalayernorm,
    ) -> None:
        """Initialize the transformer encoder block."""
        super().__init__()
        self.norm_first = norm_first
        self.diffusion_step = diffusion_step
        canonical_timestep = normalize_timestep_embedding(timestep_type)
        self.timestep_type = canonical_timestep
        self.self_attn = nn.MultiheadAttention(
            d_model, nhead, dropout=dropout, batch_first=batch_first
        )
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        match canonical_timestep:
            case None:
                self.norm1 = nn.LayerNorm(d_model, eps=1e-5)
            case (
                TimestepEmbeddingType.adalayernorm
                | TimestepEmbeddingType.adalayernorm_abs
                | TimestepEmbeddingType.adalayernorm_mlp
            ):
                self.norm1 = AdaLayerNorm(d_model, diffusion_step, canonical_timestep)
            case (
                TimestepEmbeddingType.adainnorm
                | TimestepEmbeddingType.adainnorm_abs
                | TimestepEmbeddingType.adainnorm_mlp
            ):
                self.norm1 = AdaInsNorm(d_model, diffusion_step, canonical_timestep)
            case _:
                assert_never(canonical_timestep)
        self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.activation = get_activation(activation)

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        src_mask: Bool[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Int[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Apply self-attention and feed-forward layers.

        Args:
            src: Input sequence.
            src_mask: Optional attention mask.
            src_key_padding_mask: Optional padding mask.
            timestep: Diffusion timestep tensor for adaptive normalization.

        Returns:
            Transformed sequence.
        """
        x = src
        if self.norm_first:
            x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
            x = x + self._sa_block(x, src_mask, src_key_padding_mask)
            x = x + self._ff_block(self.norm2(x))
            return x
        x = x + self._sa_block(x, src_mask, src_key_padding_mask)
        x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
        return self.norm2(x + self._ff_block(x))

    def _sa_block(
        self,
        x: Float[torch.Tensor, "batch tokens channels"],
        attn_mask: Bool[torch.Tensor, "..."] | None,
        key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        x = self.self_attn(
            x,
            x,
            x,
            attn_mask=attn_mask,
            key_padding_mask=key_padding_mask,
            need_weights=False,
        )[0]
        return self.dropout1(x)

    def _ff_block(
        self, x: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        return self.dropout2(
            self.linear2(self.dropout(self.activation(self.linear1(x))))
        )

__init__

__init__(
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: ActivationName
    | str
    | ActivationFn = ActivationName.relu,
    batch_first: bool = True,
    norm_first: bool = True,
    diffusion_step: int = 100,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None

Initialize the transformer encoder block.

Source code in lib/laygen/src/laygen/nn/blocks.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(
    self,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: ActivationName | str | ActivationFn = ActivationName.relu,
    batch_first: bool = True,
    norm_first: bool = True,
    diffusion_step: int = 100,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None:
    """Initialize the transformer encoder block."""
    super().__init__()
    self.norm_first = norm_first
    self.diffusion_step = diffusion_step
    canonical_timestep = normalize_timestep_embedding(timestep_type)
    self.timestep_type = canonical_timestep
    self.self_attn = nn.MultiheadAttention(
        d_model, nhead, dropout=dropout, batch_first=batch_first
    )
    self.linear1 = nn.Linear(d_model, dim_feedforward)
    self.dropout = nn.Dropout(dropout)
    self.linear2 = nn.Linear(dim_feedforward, d_model)
    match canonical_timestep:
        case None:
            self.norm1 = nn.LayerNorm(d_model, eps=1e-5)
        case (
            TimestepEmbeddingType.adalayernorm
            | TimestepEmbeddingType.adalayernorm_abs
            | TimestepEmbeddingType.adalayernorm_mlp
        ):
            self.norm1 = AdaLayerNorm(d_model, diffusion_step, canonical_timestep)
        case (
            TimestepEmbeddingType.adainnorm
            | TimestepEmbeddingType.adainnorm_abs
            | TimestepEmbeddingType.adainnorm_mlp
        ):
            self.norm1 = AdaInsNorm(d_model, diffusion_step, canonical_timestep)
        case _:
            assert_never(canonical_timestep)
    self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
    self.dropout1 = nn.Dropout(dropout)
    self.dropout2 = nn.Dropout(dropout)
    self.activation = get_activation(activation)

forward

forward(
    src: Float[Tensor, "batch tokens channels"],
    src_mask: Bool[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Int[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Apply self-attention and feed-forward layers.

Parameters:

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

Input sequence.

required
src_mask Bool[Tensor, '...'] | None

Optional attention mask.

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

Optional padding mask.

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

Diffusion timestep tensor for adaptive normalization.

None

Returns:

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

Transformed sequence.

Source code in lib/laygen/src/laygen/nn/blocks.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    src_mask: Bool[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Int[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Apply self-attention and feed-forward layers.

    Args:
        src: Input sequence.
        src_mask: Optional attention mask.
        src_key_padding_mask: Optional padding mask.
        timestep: Diffusion timestep tensor for adaptive normalization.

    Returns:
        Transformed sequence.
    """
    x = src
    if self.norm_first:
        x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
        x = x + self._sa_block(x, src_mask, src_key_padding_mask)
        x = x + self._ff_block(self.norm2(x))
        return x
    x = x + self._sa_block(x, src_mask, src_key_padding_mask)
    x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
    return self.norm2(x + self._ff_block(x))

get_activation

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

Return the activation callable for a supported activation name.

Origin

The gelu2 branch resolves to Transformers ACT2FN["quick_gelu"], which is formula-equivalent to VQ-Diffusion's GELU2 implementation.

Parameters:

Name Type Description Default
name ActivationName | str | ActivationFn

Activation enum, string value, or callable.

required

Returns:

Type Description
ActivationFn

Activation callable.

Raises:

Type Description
ValueError

If the activation name is unsupported.

Source code in lib/laygen/src/laygen/nn/activations.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def get_activation(name: ActivationName | str | ActivationFn) -> ActivationFn:
    """Return the activation callable for a supported activation name.

    Origin:
        The ``gelu2`` branch resolves to Transformers ``ACT2FN["quick_gelu"]``,
        which is formula-equivalent to VQ-Diffusion's GELU2 implementation.

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

    Returns:
        Activation callable.

    Raises:
        ValueError: If the activation name is unsupported.
    """
    canonical = normalize_activation(name)
    if callable(canonical):
        return canonical
    if canonical is ActivationName.relu:
        return F.relu
    if canonical is ActivationName.gelu:
        return F.gelu
    if canonical is ActivationName.gelu2:
        return cast(ActivationFn, ACT2FN["quick_gelu"])
    assert_never(canonical)

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

clone_module_list

clone_module_list(module: Module, n: int) -> nn.ModuleList

Return n deep-copied modules in a ModuleList.

Origin

This is the mechanical clone helper used by VQ-Diffusion-derived LayoutDM/LACE transformer stacks and by the LayoutFlow checkpoint backbone.

Parameters:

Name Type Description Default
module Module

Module to clone.

required
n int

Number of clones.

required

Returns:

Type Description
ModuleList

ModuleList containing independent deep copies.

Source code in lib/laygen/src/laygen/nn/module_utils.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def clone_module_list(module: nn.Module, n: int) -> nn.ModuleList:
    """Return ``n`` deep-copied modules in a ``ModuleList``.

    Origin:
        This is the mechanical clone helper used by VQ-Diffusion-derived
        LayoutDM/LACE transformer stacks and by the LayoutFlow checkpoint backbone.

    Args:
        module: Module to clone.
        n: Number of clones.

    Returns:
        ModuleList containing independent deep copies.
    """
    return nn.ModuleList(copy.deepcopy(module) for _ in range(n))

activations

Activation helpers shared by layout-generation transformer modules.

The gelu2 alias follows Microsoft VQ-Diffusion's GELU2/QuickGELU activation used by the LayoutDM, LACE, and LayoutFlow checkpoint backbones.

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()

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

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

get_activation

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

Return the activation callable for a supported activation name.

Origin

The gelu2 branch resolves to Transformers ACT2FN["quick_gelu"], which is formula-equivalent to VQ-Diffusion's GELU2 implementation.

Parameters:

Name Type Description Default
name ActivationName | str | ActivationFn

Activation enum, string value, or callable.

required

Returns:

Type Description
ActivationFn

Activation callable.

Raises:

Type Description
ValueError

If the activation name is unsupported.

Source code in lib/laygen/src/laygen/nn/activations.py
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def get_activation(name: ActivationName | str | ActivationFn) -> ActivationFn:
    """Return the activation callable for a supported activation name.

    Origin:
        The ``gelu2`` branch resolves to Transformers ``ACT2FN["quick_gelu"]``,
        which is formula-equivalent to VQ-Diffusion's GELU2 implementation.

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

    Returns:
        Activation callable.

    Raises:
        ValueError: If the activation name is unsupported.
    """
    canonical = normalize_activation(name)
    if callable(canonical):
        return canonical
    if canonical is ActivationName.relu:
        return F.relu
    if canonical is ActivationName.gelu:
        return F.gelu
    if canonical is ActivationName.gelu2:
        return cast(ActivationFn, ACT2FN["quick_gelu"])
    assert_never(canonical)

blocks

Transformer encoder blocks shared by layout-generation models.

The timestep-aware encoder layer follows Microsoft VQ-Diffusion's transformer_utils.Block structure as carried by LayoutDM, LACE, and Layout-Corrector reference implementations.

TimestepTransformerEncoderLayer

Bases: Module

Transformer encoder block with optional adaptive normalization.

Origin

This class follows VQ-Diffusion Block rather than Diffusers BasicTransformerBlock so checkpoint keys and adaptive norm call conventions stay compatible with LayoutDM, LACE, and Layout-Corrector.

Parameters:

Name Type Description Default
d_model int

Hidden dimension.

512
nhead int

Number of attention heads.

8
dim_feedforward int

Feed-forward hidden dimension.

2048
dropout float

Dropout probability.

0.0
activation ActivationName | str | ActivationFn

Feed-forward activation name or callable.

relu
batch_first bool

Whether inputs use (batch, seq, dim) order.

True
norm_first bool

Whether to use pre-norm residual blocks.

True
diffusion_step int

Maximum diffusion timestep.

100
timestep_type TimestepEmbeddingType | str | None

Timestep-conditioned normalization variant.

adalayernorm
Source code in lib/laygen/src/laygen/nn/blocks.py
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 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
class TimestepTransformerEncoderLayer(nn.Module):
    """Transformer encoder block with optional adaptive normalization.

    Origin:
        This class follows VQ-Diffusion ``Block`` rather than Diffusers
        ``BasicTransformerBlock`` so checkpoint keys and adaptive norm call
        conventions stay compatible with LayoutDM, LACE, and Layout-Corrector.

    Args:
        d_model: Hidden dimension.
        nhead: Number of attention heads.
        dim_feedforward: Feed-forward hidden dimension.
        dropout: Dropout probability.
        activation: Feed-forward activation name or callable.
        batch_first: Whether inputs use ``(batch, seq, dim)`` order.
        norm_first: Whether to use pre-norm residual blocks.
        diffusion_step: Maximum diffusion timestep.
        timestep_type: Timestep-conditioned normalization variant.
    """

    def __init__(
        self,
        d_model: int = 512,
        nhead: int = 8,
        dim_feedforward: int = 2048,
        dropout: float = 0.0,
        activation: ActivationName | str | ActivationFn = ActivationName.relu,
        batch_first: bool = True,
        norm_first: bool = True,
        diffusion_step: int = 100,
        timestep_type: TimestepEmbeddingType
        | str
        | None = TimestepEmbeddingType.adalayernorm,
    ) -> None:
        """Initialize the transformer encoder block."""
        super().__init__()
        self.norm_first = norm_first
        self.diffusion_step = diffusion_step
        canonical_timestep = normalize_timestep_embedding(timestep_type)
        self.timestep_type = canonical_timestep
        self.self_attn = nn.MultiheadAttention(
            d_model, nhead, dropout=dropout, batch_first=batch_first
        )
        self.linear1 = nn.Linear(d_model, dim_feedforward)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(dim_feedforward, d_model)
        match canonical_timestep:
            case None:
                self.norm1 = nn.LayerNorm(d_model, eps=1e-5)
            case (
                TimestepEmbeddingType.adalayernorm
                | TimestepEmbeddingType.adalayernorm_abs
                | TimestepEmbeddingType.adalayernorm_mlp
            ):
                self.norm1 = AdaLayerNorm(d_model, diffusion_step, canonical_timestep)
            case (
                TimestepEmbeddingType.adainnorm
                | TimestepEmbeddingType.adainnorm_abs
                | TimestepEmbeddingType.adainnorm_mlp
            ):
                self.norm1 = AdaInsNorm(d_model, diffusion_step, canonical_timestep)
            case _:
                assert_never(canonical_timestep)
        self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
        self.dropout1 = nn.Dropout(dropout)
        self.dropout2 = nn.Dropout(dropout)
        self.activation = get_activation(activation)

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        src_mask: Bool[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Int[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Apply self-attention and feed-forward layers.

        Args:
            src: Input sequence.
            src_mask: Optional attention mask.
            src_key_padding_mask: Optional padding mask.
            timestep: Diffusion timestep tensor for adaptive normalization.

        Returns:
            Transformed sequence.
        """
        x = src
        if self.norm_first:
            x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
            x = x + self._sa_block(x, src_mask, src_key_padding_mask)
            x = x + self._ff_block(self.norm2(x))
            return x
        x = x + self._sa_block(x, src_mask, src_key_padding_mask)
        x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
        return self.norm2(x + self._ff_block(x))

    def _sa_block(
        self,
        x: Float[torch.Tensor, "batch tokens channels"],
        attn_mask: Bool[torch.Tensor, "..."] | None,
        key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        x = self.self_attn(
            x,
            x,
            x,
            attn_mask=attn_mask,
            key_padding_mask=key_padding_mask,
            need_weights=False,
        )[0]
        return self.dropout1(x)

    def _ff_block(
        self, x: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        return self.dropout2(
            self.linear2(self.dropout(self.activation(self.linear1(x))))
        )
__init__
__init__(
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: ActivationName
    | str
    | ActivationFn = ActivationName.relu,
    batch_first: bool = True,
    norm_first: bool = True,
    diffusion_step: int = 100,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None

Initialize the transformer encoder block.

Source code in lib/laygen/src/laygen/nn/blocks.py
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def __init__(
    self,
    d_model: int = 512,
    nhead: int = 8,
    dim_feedforward: int = 2048,
    dropout: float = 0.0,
    activation: ActivationName | str | ActivationFn = ActivationName.relu,
    batch_first: bool = True,
    norm_first: bool = True,
    diffusion_step: int = 100,
    timestep_type: TimestepEmbeddingType
    | str
    | None = TimestepEmbeddingType.adalayernorm,
) -> None:
    """Initialize the transformer encoder block."""
    super().__init__()
    self.norm_first = norm_first
    self.diffusion_step = diffusion_step
    canonical_timestep = normalize_timestep_embedding(timestep_type)
    self.timestep_type = canonical_timestep
    self.self_attn = nn.MultiheadAttention(
        d_model, nhead, dropout=dropout, batch_first=batch_first
    )
    self.linear1 = nn.Linear(d_model, dim_feedforward)
    self.dropout = nn.Dropout(dropout)
    self.linear2 = nn.Linear(dim_feedforward, d_model)
    match canonical_timestep:
        case None:
            self.norm1 = nn.LayerNorm(d_model, eps=1e-5)
        case (
            TimestepEmbeddingType.adalayernorm
            | TimestepEmbeddingType.adalayernorm_abs
            | TimestepEmbeddingType.adalayernorm_mlp
        ):
            self.norm1 = AdaLayerNorm(d_model, diffusion_step, canonical_timestep)
        case (
            TimestepEmbeddingType.adainnorm
            | TimestepEmbeddingType.adainnorm_abs
            | TimestepEmbeddingType.adainnorm_mlp
        ):
            self.norm1 = AdaInsNorm(d_model, diffusion_step, canonical_timestep)
        case _:
            assert_never(canonical_timestep)
    self.norm2 = nn.LayerNorm(d_model, eps=1e-5)
    self.dropout1 = nn.Dropout(dropout)
    self.dropout2 = nn.Dropout(dropout)
    self.activation = get_activation(activation)
forward
forward(
    src: Float[Tensor, "batch tokens channels"],
    src_mask: Bool[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Int[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Apply self-attention and feed-forward layers.

Parameters:

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

Input sequence.

required
src_mask Bool[Tensor, '...'] | None

Optional attention mask.

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

Optional padding mask.

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

Diffusion timestep tensor for adaptive normalization.

None

Returns:

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

Transformed sequence.

Source code in lib/laygen/src/laygen/nn/blocks.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    src_mask: Bool[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Int[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Apply self-attention and feed-forward layers.

    Args:
        src: Input sequence.
        src_mask: Optional attention mask.
        src_key_padding_mask: Optional padding mask.
        timestep: Diffusion timestep tensor for adaptive normalization.

    Returns:
        Transformed sequence.
    """
    x = src
    if self.norm_first:
        x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
        x = x + self._sa_block(x, src_mask, src_key_padding_mask)
        x = x + self._ff_block(self.norm2(x))
        return x
    x = x + self._sa_block(x, src_mask, src_key_padding_mask)
    x = self.norm1(x, timestep) if self.timestep_type else self.norm1(x)
    return self.norm2(x + self._ff_block(x))

TimestepTransformerEncoder

Bases: Module

Stack of cloned timestep transformer encoder layers.

Origin

This is the VQ-Diffusion-style cloned TransformerEncoder wrapper used by LayoutDM and Layout-Corrector around the shared Block layer.

Parameters:

Name Type Description Default
encoder_layer TimestepTransformerEncoderLayer

Layer to clone for the stack.

required
num_layers int

Number of cloned layers.

required
norm Module | None

Optional final normalization module.

None
Source code in lib/laygen/src/laygen/nn/blocks.py
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
class TimestepTransformerEncoder(nn.Module):
    """Stack of cloned timestep transformer encoder layers.

    Origin:
        This is the VQ-Diffusion-style cloned ``TransformerEncoder`` wrapper
        used by LayoutDM and Layout-Corrector around the shared ``Block`` layer.

    Args:
        encoder_layer: Layer to clone for the stack.
        num_layers: Number of cloned layers.
        norm: Optional final normalization module.
    """

    def __init__(
        self,
        encoder_layer: TimestepTransformerEncoderLayer,
        num_layers: int,
        norm: nn.Module | None = None,
    ) -> None:
        """Initialize a transformer encoder stack."""
        super().__init__()
        self.layers = clone_module_list(encoder_layer, num_layers)
        self.num_layers = num_layers
        self.norm = norm

    def forward(
        self,
        src: Float[torch.Tensor, "batch tokens channels"],
        mask: Bool[torch.Tensor, "..."] | None = None,
        src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
        timestep: Int[torch.Tensor, "batch"] | None = None,
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Run hidden states through all encoder layers."""
        output = src
        for layer in self.layers:
            output = layer(
                output,
                src_mask=mask,
                src_key_padding_mask=src_key_padding_mask,
                timestep=timestep,
            )
        return self.norm(output) if self.norm is not None else output
__init__
__init__(
    encoder_layer: TimestepTransformerEncoderLayer,
    num_layers: int,
    norm: Module | None = None,
) -> None

Initialize a transformer encoder stack.

Source code in lib/laygen/src/laygen/nn/blocks.py
155
156
157
158
159
160
161
162
163
164
165
def __init__(
    self,
    encoder_layer: TimestepTransformerEncoderLayer,
    num_layers: int,
    norm: nn.Module | None = None,
) -> None:
    """Initialize a transformer encoder stack."""
    super().__init__()
    self.layers = clone_module_list(encoder_layer, num_layers)
    self.num_layers = num_layers
    self.norm = norm
forward
forward(
    src: Float[Tensor, "batch tokens channels"],
    mask: Bool[Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[Tensor, "batch tokens"]
    | None = None,
    timestep: Int[Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]

Run hidden states through all encoder layers.

Source code in lib/laygen/src/laygen/nn/blocks.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
def forward(
    self,
    src: Float[torch.Tensor, "batch tokens channels"],
    mask: Bool[torch.Tensor, "..."] | None = None,
    src_key_padding_mask: Bool[torch.Tensor, "batch tokens"] | None = None,
    timestep: Int[torch.Tensor, "batch"] | None = None,
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Run hidden states through all encoder layers."""
    output = src
    for layer in self.layers:
        output = layer(
            output,
            src_mask=mask,
            src_key_padding_mask=src_key_padding_mask,
            timestep=timestep,
        )
    return self.norm(output) if self.norm is not None else output

embeddings

Embedding modules shared by layout-generation models.

SinusoidalPosEmb follows the VQ-Diffusion-derived timestep embedding used by the LayoutDM and LACE checkpoint backbones. ElementPositionalEmbedding is a LayoutDM-specific element/attribute position embedding.

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()

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)

ElementPositionalEmbedding

Bases: Module

Learned element and attribute positional embedding.

Origin

This learned element/attribute positional embedding is specific to CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

Parameters:

Name Type Description Default
dim_model int

Embedding dimension.

required
max_token_length int

Maximum flattened token sequence length.

required
n_attr_per_elem int

Number of attributes per layout element.

5
Source code in lib/laygen/src/laygen/nn/embeddings.py
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
class ElementPositionalEmbedding(nn.Module):
    """Learned element and attribute positional embedding.

    Origin:
        This learned element/attribute positional embedding is specific to
        CyberAgentAILab LayoutDM and is reused by Layout-Corrector.

    Args:
        dim_model: Embedding dimension.
        max_token_length: Maximum flattened token sequence length.
        n_attr_per_elem: Number of attributes per layout element.
    """

    def __init__(
        self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
    ) -> None:
        """Initialize element and attribute embedding parameters."""
        super().__init__()
        self.n_elem = max_token_length // n_attr_per_elem
        self.n_attr_per_elem = n_attr_per_elem
        self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
        self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))

    def forward(
        self, h: Float[torch.Tensor, "batch tokens channels"]
    ) -> Float[torch.Tensor, "batch tokens channels"]:
        """Return positional embeddings matching hidden-state length.

        Args:
            h: Hidden states shaped ``(batch, sequence, dim)``.

        Returns:
            Positional embedding tensor shaped like ``h``.
        """
        batch, seq_len = h.shape[:2]
        elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
        attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
        emb = (elem_emb + attr_emb)[:seq_len]
        return repeat(emb, "s d -> b s d", b=batch)

    @property
    def no_decay_param_names(self) -> list[str]:
        """Return parameter names that should skip weight decay."""
        return ["elem_emb", "attr_emb"]
no_decay_param_names property
no_decay_param_names: list[str]

Return parameter names that should skip weight decay.

__init__
__init__(
    dim_model: int,
    max_token_length: int,
    n_attr_per_elem: int = 5,
) -> None

Initialize element and attribute embedding parameters.

Source code in lib/laygen/src/laygen/nn/embeddings.py
115
116
117
118
119
120
121
122
123
def __init__(
    self, dim_model: int, max_token_length: int, n_attr_per_elem: int = 5
) -> None:
    """Initialize element and attribute embedding parameters."""
    super().__init__()
    self.n_elem = max_token_length // n_attr_per_elem
    self.n_attr_per_elem = n_attr_per_elem
    self.elem_emb = nn.Parameter(torch.rand(self.n_elem, dim_model))
    self.attr_emb = nn.Parameter(torch.rand(self.n_attr_per_elem, dim_model))
forward
forward(
    h: Float[Tensor, "batch tokens channels"],
) -> Float[torch.Tensor, "batch tokens channels"]

Return positional embeddings matching hidden-state length.

Parameters:

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

Hidden states shaped (batch, sequence, dim).

required

Returns:

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

Positional embedding tensor shaped like h.

Source code in lib/laygen/src/laygen/nn/embeddings.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
def forward(
    self, h: Float[torch.Tensor, "batch tokens channels"]
) -> Float[torch.Tensor, "batch tokens channels"]:
    """Return positional embeddings matching hidden-state length.

    Args:
        h: Hidden states shaped ``(batch, sequence, dim)``.

    Returns:
        Positional embedding tensor shaped like ``h``.
    """
    batch, seq_len = h.shape[:2]
    elem_emb = repeat(self.elem_emb, "s d -> (s x) d", x=self.n_attr_per_elem)
    attr_emb = repeat(self.attr_emb, "x d -> (s x) d", s=self.n_elem)
    emb = (elem_emb + attr_emb)[:seq_len]
    return repeat(emb, "s d -> b s d", b=batch)

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

module_utils

Small module-construction helpers.

clone_module_list is the deep-copy ModuleList helper used by the VQ-Diffusion-derived LayoutDM/LACE blocks and the LayoutFlow checkpoint backbone.

clone_module_list

clone_module_list(module: Module, n: int) -> nn.ModuleList

Return n deep-copied modules in a ModuleList.

Origin

This is the mechanical clone helper used by VQ-Diffusion-derived LayoutDM/LACE transformer stacks and by the LayoutFlow checkpoint backbone.

Parameters:

Name Type Description Default
module Module

Module to clone.

required
n int

Number of clones.

required

Returns:

Type Description
ModuleList

ModuleList containing independent deep copies.

Source code in lib/laygen/src/laygen/nn/module_utils.py
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
def clone_module_list(module: nn.Module, n: int) -> nn.ModuleList:
    """Return ``n`` deep-copied modules in a ``ModuleList``.

    Origin:
        This is the mechanical clone helper used by VQ-Diffusion-derived
        LayoutDM/LACE transformer stacks and by the LayoutFlow checkpoint backbone.

    Args:
        module: Module to clone.
        n: Number of clones.

    Returns:
        ModuleList containing independent deep copies.
    """
    return nn.ModuleList(copy.deepcopy(module) for _ in range(n))

norms

Adaptive normalization layers shared by layout-generation models.

These adaptive normalization layers follow Microsoft VQ-Diffusion's AdaLayerNorm/AdaInsNorm utilities used by the LayoutDM and LACE checkpoint backbones.

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

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
    )

pipelines

Pipeline base and output types for layout-generation packages.

LayoutGenerationOutput dataclass

Bases: ModelOutput

Canonical layout-generation output for Transformers-style APIs.

Attributes:

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

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

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

Dataset-local integer labels with shape (batch, elements).

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

Boolean valid-element mask with shape (batch, elements).

id2label dict[int, str]

Mapping from integer label ids to display names.

sequences object | None

Optional raw token sequences.

scores object | None

Optional per-token or per-element scores.

trajectory object | None

Optional sampling trajectory.

intermediates object | None

Optional model-specific debug or auxiliary data.

Examples:

>>> import numpy as np
>>> output = LayoutGenerationOutput(
...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
...     labels=np.zeros((1, 1), dtype=np.int64),
...     mask=np.ones((1, 1), dtype=bool),
...     id2label={0: "text"},
... )
>>> output["bbox"].shape
(1, 1, 4)
Source code in lib/laygen/src/laygen/modeling_outputs.py
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@dataclass
class LayoutGenerationOutput(ModelOutput):
    """Canonical layout-generation output for Transformers-style APIs.

    Attributes:
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        labels: Dataset-local integer labels with shape ``(batch, elements)``.
        mask: Boolean valid-element mask with shape ``(batch, elements)``.
        id2label: Mapping from integer label ids to display names.
        sequences: Optional raw token sequences.
        scores: Optional per-token or per-element scores.
        trajectory: Optional sampling trajectory.
        intermediates: Optional model-specific debug or auxiliary data.

    Examples:
        >>> import numpy as np
        >>> output = LayoutGenerationOutput(
        ...     bbox=np.zeros((1, 1, 4), dtype=np.float32),
        ...     labels=np.zeros((1, 1), dtype=np.int64),
        ...     mask=np.ones((1, 1), dtype=bool),
        ...     id2label={0: "text"},
        ... )
        >>> output["bbox"].shape
        (1, 1, 4)
    """

    bbox: (
        Float[np.ndarray, "batch elements 4"] | Float[torch.Tensor, "batch elements 4"]
    )
    labels: Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"] = (
        cast(
            'Int[np.ndarray, "batch elements"] | Int[torch.Tensor, "batch elements"]',
            None,
        )
    )
    mask: Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"] = (
        cast(
            'Bool[np.ndarray, "batch elements"] | Bool[torch.Tensor, "batch elements"]',
            None,
        )
    )
    id2label: dict[int, str] = cast(dict[int, str], None)
    sequences: object | None = None
    scores: object | None = None
    trajectory: object | None = None
    intermediates: object | None = None

LayoutGenerationPipeline

Bases: ABC

Base class for Transformers-side layout-generation pipelines.

Subclasses declare checkpoint components with component_specs, implement _from_pretrained_components, and put generation orchestration in __call__. The public __call__ contract is to return laygen.modeling_outputs.LayoutGenerationOutput for layout-generation outputs.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config, usually a PretrainedConfig subclass.

required

Examples:

>>> from transformers import PretrainedConfig
>>> class ToyPipeline(LayoutGenerationPipeline):
...     config_class = PretrainedConfig
...     @classmethod
...     def _from_pretrained_components(cls, *, config, components):
...         return cls(config)
...     def __call__(self):
...         import torch
...         return LayoutGenerationOutput(
...             bbox=torch.zeros(1, 1, 4),
...             labels=torch.zeros(1, 1, dtype=torch.long),
...             mask=torch.ones(1, 1, dtype=torch.bool),
...             id2label={0: "text"},
...         )
>>> isinstance(ToyPipeline(PretrainedConfig()).config, PretrainedConfig)
True
Source code in lib/laygen/src/laygen/pipelines/base.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
class LayoutGenerationPipeline(ABC):
    """Base class for Transformers-side layout-generation pipelines.

    Subclasses declare checkpoint components with `component_specs`, implement
    `_from_pretrained_components`, and put generation orchestration in
    `__call__`. The public `__call__` contract is to return
    `laygen.modeling_outputs.LayoutGenerationOutput` for layout-generation
    outputs.

    Args:
        config: Root pipeline config, usually a `PretrainedConfig` subclass.

    Examples:
        >>> from transformers import PretrainedConfig
        >>> class ToyPipeline(LayoutGenerationPipeline):
        ...     config_class = PretrainedConfig
        ...     @classmethod
        ...     def _from_pretrained_components(cls, *, config, components):
        ...         return cls(config)
        ...     def __call__(self):
        ...         import torch
        ...         return LayoutGenerationOutput(
        ...             bbox=torch.zeros(1, 1, 4),
        ...             labels=torch.zeros(1, 1, dtype=torch.long),
        ...             mask=torch.ones(1, 1, dtype=torch.bool),
        ...             id2label={0: "text"},
        ...         )
        >>> isinstance(ToyPipeline(PretrainedConfig()).config, PretrainedConfig)
        True
    """

    config_class: ClassVar[type[PretrainedConfig]] = PretrainedConfig
    component_specs: ClassVar[Mapping[str, PipelineComponentSpec]] = {}

    config: PretrainedConfig
    device: torch.device | None
    dtype: torch.dtype | None

    def __init__(self, config: PretrainedConfig) -> None:
        """Initialize root config and runtime placement metadata.

        Args:
            config: Root pipeline config.
        """
        self.config = config
        self.device = None
        self.dtype = None

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        *,
        local_files_only: bool = False,
        config: PretrainedConfig | None = None,
        components: Mapping[str, PipelineComponent] | None = None,
    ) -> Self:
        """Load a pipeline from a checkpoint root and declared subfolders.

        Args:
            pretrained_model_name_or_path: Checkpoint root.
            local_files_only: Whether to avoid network access.
            config: Optional preloaded root config.
            components: Optional preloaded components keyed by spec name.

        Returns:
            Loaded pipeline instance.

        Raises:
            FileNotFoundError: If a required component marker file is missing.
            TypeError: If `config` is not compatible with `config_class`.
        """
        root = Path(pretrained_model_name_or_path)
        source = pretrained_model_name_or_path
        pipeline_config = cls._load_pipeline_config(
            source,
            local_files_only=local_files_only,
            config=config,
        )
        loaded_components = cls._load_pipeline_components(
            root,
            source,
            pipeline_config,
            local_files_only=local_files_only,
            components=components or {},
        )
        return cls._from_pretrained_components(
            config=pipeline_config,
            components=loaded_components,
        )

    @classmethod
    def _load_pipeline_config(
        cls,
        source: str | Path,
        *,
        local_files_only: bool,
        config: PretrainedConfig | None,
    ) -> PretrainedConfig:
        if config is None:
            return cls.config_class.from_pretrained(
                source,
                local_files_only=local_files_only,
            )
        if isinstance(config, cls.config_class):
            return config
        if isinstance(config, PretrainedConfig):
            return cls.config_class.from_dict(config.to_dict())
        raise TypeError(f"config must be a {cls.config_class.__name__}")

    @classmethod
    def _load_pipeline_components(
        cls,
        root: Path,
        source: str | Path,
        config: PretrainedConfig,
        *,
        local_files_only: bool,
        components: Mapping[str, PipelineComponent],
    ) -> dict[str, PipelineComponent | None]:
        loaded: dict[str, PipelineComponent | None] = {}
        for name, spec in cls.component_specs.items():
            if name in components:
                loaded[name] = components[name]
                continue
            if spec.loader is None:
                loaded[name] = None
                continue
            if root.is_dir():
                component_path = spec.component_path(root, config)
                marker = (
                    component_path / spec.marker_file
                    if spec.marker_file is not None
                    else None
                )
                if marker is not None and not marker.exists():
                    if spec.required:
                        raise FileNotFoundError(
                            f"Required pipeline component '{name}' is missing: {marker}"
                        )

                    loaded[name] = None
                    continue
                loaded[name] = spec.loader(
                    component_path,
                    local_files_only=local_files_only,
                )
                continue
            loaded[name] = spec.loader(
                source,
                local_files_only=local_files_only,
                subfolder=spec.component_subfolder(config),
            )
        return loaded

    @classmethod
    @abstractmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> Self:
        """Build a pipeline from a root config and loaded components.

        Args:
            config: Loaded root config.
            components: Components keyed by `component_specs` names.

        Returns:
            Loaded pipeline instance.
        """

    def save_pretrained(
        self,
        save_directory: str | Path,
        *,
        is_main_process: bool = True,
    ) -> None:
        """Save root config and declared components.

        Args:
            save_directory: Checkpoint root directory.
            is_main_process: Whether model-like components should perform main
                process writes.

        Raises:
            TypeError: If a component does not implement `save_pretrained`.
            ValueError: If a required component attribute is missing.
        """
        root = Path(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        self.config.save_pretrained(root)
        for name, spec in self.component_specs.items():
            component = getattr(self, spec.attribute_name, None)
            if component is None:
                if spec.required:
                    raise ValueError(f"Required pipeline component '{name}' is None")

                continue
            component_path = spec.component_path(root, self.config)
            component_path.mkdir(parents=True, exist_ok=True)
            if spec.save_with_is_main_process:
                if not isinstance(component, SavePretrainedWithMainProcess):
                    raise TypeError(
                        f"Pipeline component '{name}' does not support save_pretrained"
                    )

                component.save_pretrained(
                    component_path,
                    is_main_process=is_main_process,
                )
            else:
                if not isinstance(component, SavePretrainedPlain):
                    raise TypeError(
                        f"Pipeline component '{name}' does not support save_pretrained"
                    )

                component.save_pretrained(component_path)

    def to(
        self,
        device: str | torch.device | None = None,
        dtype: torch.dtype | None = None,
    ) -> Self:
        """Move movable components to a device and/or dtype.

        Args:
            device: Target torch device.
            dtype: Target torch dtype.

        Returns:
            This pipeline instance.
        """
        if device is not None:
            self.device = torch.device(device)
        if dtype is not None:
            self.dtype = dtype
        if self.device is None and self.dtype is None:
            return self
        for component in self._pipeline_component_values():
            if isinstance(component, TorchMovable):
                component.to(device=self.device, dtype=self.dtype)
        return self

    def prepare_generator(
        self,
        *,
        generator: torch.Generator | None = None,
        seed: int | None = None,
        device: str | torch.device | None = None,
    ) -> torch.Generator | None:
        """Apply generator-over-seed precedence for generation calls.

        Args:
            generator: Explicit torch generator. When provided, `seed` is
                ignored.
            seed: Integer seed used only when `generator` is absent.
            device: Optional device for a newly created generator. If omitted,
                the pipeline's current device is used.

        Returns:
            The explicit generator, a seeded generator when a device is known,
            or `None` after setting global Transformers/PyTorch seed state.
        """
        if generator is not None:
            return generator
        if seed is None:
            return None
        set_seed(seed)
        generator_device = torch.device(device) if device is not None else self.device
        if generator_device is None:
            return None
        return torch.Generator(device=generator_device).manual_seed(seed)

    def _pipeline_component_values(self) -> tuple[PipelineComponent, ...]:
        values: list[PipelineComponent] = []
        for spec in self.component_specs.values():
            component = getattr(self, spec.attribute_name, None)
            if component is not None:
                values.append(cast(PipelineComponent, component))
        return tuple(values)

    @abstractmethod
    def __call__(self) -> LayoutGenerationOutput:
        """Generate a layout.

        Returns:
            Layout generation output in the canonical Transformers-style schema.
        """

__init__

__init__(config: PretrainedConfig) -> None

Initialize root config and runtime placement metadata.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config.

required
Source code in lib/laygen/src/laygen/pipelines/base.py
249
250
251
252
253
254
255
256
257
def __init__(self, config: PretrainedConfig) -> None:
    """Initialize root config and runtime placement metadata.

    Args:
        config: Root pipeline config.
    """
    self.config = config
    self.device = None
    self.dtype = None

from_pretrained classmethod

from_pretrained(
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    config: PretrainedConfig | None = None,
    components: Mapping[str, PipelineComponent]
    | None = None,
) -> Self

Load a pipeline from a checkpoint root and declared subfolders.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Checkpoint root.

required
local_files_only bool

Whether to avoid network access.

False
config PretrainedConfig | None

Optional preloaded root config.

None
components Mapping[str, PipelineComponent] | None

Optional preloaded components keyed by spec name.

None

Returns:

Type Description
Self

Loaded pipeline instance.

Raises:

Type Description
FileNotFoundError

If a required component marker file is missing.

TypeError

If config is not compatible with config_class.

Source code in lib/laygen/src/laygen/pipelines/base.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    config: PretrainedConfig | None = None,
    components: Mapping[str, PipelineComponent] | None = None,
) -> Self:
    """Load a pipeline from a checkpoint root and declared subfolders.

    Args:
        pretrained_model_name_or_path: Checkpoint root.
        local_files_only: Whether to avoid network access.
        config: Optional preloaded root config.
        components: Optional preloaded components keyed by spec name.

    Returns:
        Loaded pipeline instance.

    Raises:
        FileNotFoundError: If a required component marker file is missing.
        TypeError: If `config` is not compatible with `config_class`.
    """
    root = Path(pretrained_model_name_or_path)
    source = pretrained_model_name_or_path
    pipeline_config = cls._load_pipeline_config(
        source,
        local_files_only=local_files_only,
        config=config,
    )
    loaded_components = cls._load_pipeline_components(
        root,
        source,
        pipeline_config,
        local_files_only=local_files_only,
        components=components or {},
    )
    return cls._from_pretrained_components(
        config=pipeline_config,
        components=loaded_components,
    )

save_pretrained

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

Save root config and declared components.

Parameters:

Name Type Description Default
save_directory str | Path

Checkpoint root directory.

required
is_main_process bool

Whether model-like components should perform main process writes.

True

Raises:

Type Description
TypeError

If a component does not implement save_pretrained.

ValueError

If a required component attribute is missing.

Source code in lib/laygen/src/laygen/pipelines/base.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
def save_pretrained(
    self,
    save_directory: str | Path,
    *,
    is_main_process: bool = True,
) -> None:
    """Save root config and declared components.

    Args:
        save_directory: Checkpoint root directory.
        is_main_process: Whether model-like components should perform main
            process writes.

    Raises:
        TypeError: If a component does not implement `save_pretrained`.
        ValueError: If a required component attribute is missing.
    """
    root = Path(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    self.config.save_pretrained(root)
    for name, spec in self.component_specs.items():
        component = getattr(self, spec.attribute_name, None)
        if component is None:
            if spec.required:
                raise ValueError(f"Required pipeline component '{name}' is None")

            continue
        component_path = spec.component_path(root, self.config)
        component_path.mkdir(parents=True, exist_ok=True)
        if spec.save_with_is_main_process:
            if not isinstance(component, SavePretrainedWithMainProcess):
                raise TypeError(
                    f"Pipeline component '{name}' does not support save_pretrained"
                )

            component.save_pretrained(
                component_path,
                is_main_process=is_main_process,
            )
        else:
            if not isinstance(component, SavePretrainedPlain):
                raise TypeError(
                    f"Pipeline component '{name}' does not support save_pretrained"
                )

            component.save_pretrained(component_path)

to

to(
    device: str | device | None = None,
    dtype: dtype | None = None,
) -> Self

Move movable components to a device and/or dtype.

Parameters:

Name Type Description Default
device str | device | None

Target torch device.

None
dtype dtype | None

Target torch dtype.

None

Returns:

Type Description
Self

This pipeline instance.

Source code in lib/laygen/src/laygen/pipelines/base.py
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def to(
    self,
    device: str | torch.device | None = None,
    dtype: torch.dtype | None = None,
) -> Self:
    """Move movable components to a device and/or dtype.

    Args:
        device: Target torch device.
        dtype: Target torch dtype.

    Returns:
        This pipeline instance.
    """
    if device is not None:
        self.device = torch.device(device)
    if dtype is not None:
        self.dtype = dtype
    if self.device is None and self.dtype is None:
        return self
    for component in self._pipeline_component_values():
        if isinstance(component, TorchMovable):
            component.to(device=self.device, dtype=self.dtype)
    return self

prepare_generator

prepare_generator(
    *,
    generator: Generator | None = None,
    seed: int | None = None,
    device: str | device | None = None,
) -> torch.Generator | None

Apply generator-over-seed precedence for generation calls.

Parameters:

Name Type Description Default
generator Generator | None

Explicit torch generator. When provided, seed is ignored.

None
seed int | None

Integer seed used only when generator is absent.

None
device str | device | None

Optional device for a newly created generator. If omitted, the pipeline's current device is used.

None

Returns:

Type Description
Generator | None

The explicit generator, a seeded generator when a device is known,

Generator | None

or None after setting global Transformers/PyTorch seed state.

Source code in lib/laygen/src/laygen/pipelines/base.py
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
def prepare_generator(
    self,
    *,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    device: str | torch.device | None = None,
) -> torch.Generator | None:
    """Apply generator-over-seed precedence for generation calls.

    Args:
        generator: Explicit torch generator. When provided, `seed` is
            ignored.
        seed: Integer seed used only when `generator` is absent.
        device: Optional device for a newly created generator. If omitted,
            the pipeline's current device is used.

    Returns:
        The explicit generator, a seeded generator when a device is known,
        or `None` after setting global Transformers/PyTorch seed state.
    """
    if generator is not None:
        return generator
    if seed is None:
        return None
    set_seed(seed)
    generator_device = torch.device(device) if device is not None else self.device
    if generator_device is None:
        return None
    return torch.Generator(device=generator_device).manual_seed(seed)

__call__ abstractmethod

__call__() -> LayoutGenerationOutput

Generate a layout.

Returns:

Type Description
LayoutGenerationOutput

Layout generation output in the canonical Transformers-style schema.

Source code in lib/laygen/src/laygen/pipelines/base.py
494
495
496
497
498
499
500
@abstractmethod
def __call__(self) -> LayoutGenerationOutput:
    """Generate a layout.

    Returns:
        Layout generation output in the canonical Transformers-style schema.
    """

PipelineComponentSpec dataclass

Declarative loading and saving rule for one pipeline component.

Parameters:

Name Type Description Default
attribute_name str

Attribute on the pipeline instance.

required
loader PipelineComponentLoader | None

Loader callable used by from_pretrained.

None
subfolder str | None

Fixed subfolder under the checkpoint root. If omitted, the root itself is used.

None
config_subfolder_attribute str | None

Config attribute that stores the subfolder name. This takes precedence over subfolder.

None
required bool

Whether missing marker files or missing instance attributes are errors.

True
marker_file str | None

File used to detect whether an optional component exists. Set to None to always call the loader.

'config.json'
save_with_is_main_process bool

Whether to pass is_main_process to the component's save_pretrained method.

True
Source code in lib/laygen/src/laygen/pipelines/base.py
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
@dataclass(frozen=True)
class PipelineComponentSpec:
    """Declarative loading and saving rule for one pipeline component.

    Args:
        attribute_name: Attribute on the pipeline instance.
        loader: Loader callable used by `from_pretrained`.
        subfolder: Fixed subfolder under the checkpoint root. If omitted, the
            root itself is used.
        config_subfolder_attribute: Config attribute that stores the subfolder
            name. This takes precedence over `subfolder`.
        required: Whether missing marker files or missing instance attributes
            are errors.
        marker_file: File used to detect whether an optional component exists.
            Set to `None` to always call the loader.
        save_with_is_main_process: Whether to pass `is_main_process` to the
            component's `save_pretrained` method.
    """

    attribute_name: str
    loader: PipelineComponentLoader | None = None
    subfolder: str | None = None
    config_subfolder_attribute: str | None = None
    required: bool = True
    marker_file: str | None = "config.json"
    save_with_is_main_process: bool = True

    def component_path(
        self,
        root: Path,
        config: PretrainedConfig,
    ) -> Path:
        """Resolve the component path under a checkpoint root.

        Args:
            root: Checkpoint root directory.
            config: Root pipeline config.

        Returns:
            Resolved root or subfolder path.

        Raises:
            TypeError: If the configured subfolder attribute is not a string.
        """
        if self.config_subfolder_attribute is not None:
            value = getattr(config, self.config_subfolder_attribute)
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.config_subfolder_attribute} must be a string subfolder"
                )

            return root / value
        if self.subfolder is not None:
            return root / self.subfolder
        return root

    def component_subfolder(self, config: PretrainedConfig) -> str | None:
        """Resolve the component subfolder for Hub-backed loading.

        Args:
            config: Root pipeline config.

        Returns:
            Component subfolder or `None` when the component lives at the root.

        Raises:
            TypeError: If the configured subfolder attribute is not a string.
        """
        if self.config_subfolder_attribute is not None:
            value = getattr(config, self.config_subfolder_attribute)
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.config_subfolder_attribute} must be a string subfolder"
                )

            return value
        return self.subfolder

component_path

component_path(
    root: Path, config: PretrainedConfig
) -> Path

Resolve the component path under a checkpoint root.

Parameters:

Name Type Description Default
root Path

Checkpoint root directory.

required
config PretrainedConfig

Root pipeline config.

required

Returns:

Type Description
Path

Resolved root or subfolder path.

Raises:

Type Description
TypeError

If the configured subfolder attribute is not a string.

Source code in lib/laygen/src/laygen/pipelines/base.py
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
def component_path(
    self,
    root: Path,
    config: PretrainedConfig,
) -> Path:
    """Resolve the component path under a checkpoint root.

    Args:
        root: Checkpoint root directory.
        config: Root pipeline config.

    Returns:
        Resolved root or subfolder path.

    Raises:
        TypeError: If the configured subfolder attribute is not a string.
    """
    if self.config_subfolder_attribute is not None:
        value = getattr(config, self.config_subfolder_attribute)
        if not isinstance(value, str):
            raise TypeError(
                f"{self.config_subfolder_attribute} must be a string subfolder"
            )

        return root / value
    if self.subfolder is not None:
        return root / self.subfolder
    return root

component_subfolder

component_subfolder(config: PretrainedConfig) -> str | None

Resolve the component subfolder for Hub-backed loading.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config.

required

Returns:

Type Description
str | None

Component subfolder or None when the component lives at the root.

Raises:

Type Description
TypeError

If the configured subfolder attribute is not a string.

Source code in lib/laygen/src/laygen/pipelines/base.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def component_subfolder(self, config: PretrainedConfig) -> str | None:
    """Resolve the component subfolder for Hub-backed loading.

    Args:
        config: Root pipeline config.

    Returns:
        Component subfolder or `None` when the component lives at the root.

    Raises:
        TypeError: If the configured subfolder attribute is not a string.
    """
    if self.config_subfolder_attribute is not None:
        value = getattr(config, self.config_subfolder_attribute)
        if not isinstance(value, str):
            raise TypeError(
                f"{self.config_subfolder_attribute} must be a string subfolder"
            )

        return value
    return self.subfolder

model_processor_component_specs

model_processor_component_specs(
    *,
    model_loader: PipelineComponentLoader,
    processor_loader: PipelineComponentLoader,
) -> dict[str, PipelineComponentSpec]

Build standard model/processor component specs for simple pipelines.

Source code in lib/laygen/src/laygen/pipelines/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def model_processor_component_specs(
    *,
    model_loader: PipelineComponentLoader,
    processor_loader: PipelineComponentLoader,
) -> dict[str, PipelineComponentSpec]:
    """Build standard model/processor component specs for simple pipelines."""
    return {
        "model": PipelineComponentSpec(
            attribute_name="model",
            loader=model_loader,
            marker_file="config.json",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=processor_loader,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
        ),
    }

base

Shared base class for Transformers-side layout-generation pipelines.

transformers.Pipeline is optimized for registered single-model tasks using a preprocess -> _forward -> postprocess contract. Layout-generation packages in this workspace often compose processors, tokenizers, and multiple standard Transformers models, while still loading from a single checkpoint root with subfolders. LayoutGenerationPipeline is the Transformers-side analogue of Diffusers' pipeline role: it owns root config metadata, component subfolder loading and saving, device and dtype movement, and seed/generator precedence. Subclasses keep the model-specific orchestration in __call__.

PipelineComponent

Bases: Protocol

Loaded component; operation-specific capabilities are checked later.

Source code in lib/laygen/src/laygen/pipelines/base.py
27
28
29
@runtime_checkable
class PipelineComponent(Protocol):
    """Loaded component; operation-specific capabilities are checked later."""

PipelineComponentLoader

Bases: Protocol

Callable that loads one pipeline component from a checkpoint path.

Source code in lib/laygen/src/laygen/pipelines/base.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
class PipelineComponentLoader(Protocol):
    """Callable that loads one pipeline component from a checkpoint path."""

    def __call__(
        self,
        pretrained_model_name_or_path: str | Path,
        *,
        local_files_only: bool = False,
        subfolder: str | None = None,
    ) -> PipelineComponent:
        """Load a component.

        Args:
            pretrained_model_name_or_path: Root checkpoint path or Hub repo id.
            local_files_only: Whether to avoid network access.
            subfolder: Optional component subfolder for Hub-backed loading.

        Returns:
            Loaded component object.
        """
__call__
__call__(
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    subfolder: str | None = None,
) -> PipelineComponent

Load a component.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Root checkpoint path or Hub repo id.

required
local_files_only bool

Whether to avoid network access.

False
subfolder str | None

Optional component subfolder for Hub-backed loading.

None

Returns:

Type Description
PipelineComponent

Loaded component object.

Source code in lib/laygen/src/laygen/pipelines/base.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
def __call__(
    self,
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    subfolder: str | None = None,
) -> PipelineComponent:
    """Load a component.

    Args:
        pretrained_model_name_or_path: Root checkpoint path or Hub repo id.
        local_files_only: Whether to avoid network access.
        subfolder: Optional component subfolder for Hub-backed loading.

    Returns:
        Loaded component object.
    """

SavePretrainedWithMainProcess

Bases: Protocol

Component protocol for model-like save_pretrained methods.

Source code in lib/laygen/src/laygen/pipelines/base.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
@runtime_checkable
class SavePretrainedWithMainProcess(Protocol):
    """Component protocol for model-like `save_pretrained` methods."""

    def save_pretrained(
        self,
        save_directory: str | Path,
        *,
        is_main_process: bool = True,
    ) -> None | tuple[str, ...]:
        """Save a component and accept the common main-process flag.

        Args:
            save_directory: Directory to write.
            is_main_process: Whether this process should perform main writes.

        Returns:
            Component-specific save result.
        """
save_pretrained
save_pretrained(
    save_directory: str | Path,
    *,
    is_main_process: bool = True,
) -> None | tuple[str, ...]

Save a component and accept the common main-process flag.

Parameters:

Name Type Description Default
save_directory str | Path

Directory to write.

required
is_main_process bool

Whether this process should perform main writes.

True

Returns:

Type Description
None | tuple[str, ...]

Component-specific save result.

Source code in lib/laygen/src/laygen/pipelines/base.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
def save_pretrained(
    self,
    save_directory: str | Path,
    *,
    is_main_process: bool = True,
) -> None | tuple[str, ...]:
    """Save a component and accept the common main-process flag.

    Args:
        save_directory: Directory to write.
        is_main_process: Whether this process should perform main writes.

    Returns:
        Component-specific save result.
    """

SavePretrainedPlain

Bases: Protocol

Component protocol for processor-like save_pretrained methods.

Source code in lib/laygen/src/laygen/pipelines/base.py
75
76
77
78
79
80
81
82
83
84
85
86
87
@runtime_checkable
class SavePretrainedPlain(Protocol):
    """Component protocol for processor-like `save_pretrained` methods."""

    def save_pretrained(self, save_directory: str | Path) -> None | tuple[str, ...]:
        """Save a component.

        Args:
            save_directory: Directory to write.

        Returns:
            Component-specific save result.
        """
save_pretrained
save_pretrained(
    save_directory: str | Path,
) -> None | tuple[str, ...]

Save a component.

Parameters:

Name Type Description Default
save_directory str | Path

Directory to write.

required

Returns:

Type Description
None | tuple[str, ...]

Component-specific save result.

Source code in lib/laygen/src/laygen/pipelines/base.py
79
80
81
82
83
84
85
86
87
def save_pretrained(self, save_directory: str | Path) -> None | tuple[str, ...]:
    """Save a component.

    Args:
        save_directory: Directory to write.

    Returns:
        Component-specific save result.
    """

TorchMovable

Bases: Protocol

Component protocol for objects that can move device and dtype.

Source code in lib/laygen/src/laygen/pipelines/base.py
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
@runtime_checkable
class TorchMovable(Protocol):
    """Component protocol for objects that can move device and dtype."""

    def to(
        self,
        *,
        device: torch.device | None = None,
        dtype: torch.dtype | None = None,
    ) -> Self:
        """Move a component.

        Args:
            device: Target torch device.
            dtype: Target torch dtype.

        Returns:
            Component-specific move result.
        """
to
to(
    *,
    device: device | None = None,
    dtype: dtype | None = None,
) -> Self

Move a component.

Parameters:

Name Type Description Default
device device | None

Target torch device.

None
dtype dtype | None

Target torch dtype.

None

Returns:

Type Description
Self

Component-specific move result.

Source code in lib/laygen/src/laygen/pipelines/base.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
def to(
    self,
    *,
    device: torch.device | None = None,
    dtype: torch.dtype | None = None,
) -> Self:
    """Move a component.

    Args:
        device: Target torch device.
        dtype: Target torch dtype.

    Returns:
        Component-specific move result.
    """

PipelineComponentSpec dataclass

Declarative loading and saving rule for one pipeline component.

Parameters:

Name Type Description Default
attribute_name str

Attribute on the pipeline instance.

required
loader PipelineComponentLoader | None

Loader callable used by from_pretrained.

None
subfolder str | None

Fixed subfolder under the checkpoint root. If omitted, the root itself is used.

None
config_subfolder_attribute str | None

Config attribute that stores the subfolder name. This takes precedence over subfolder.

None
required bool

Whether missing marker files or missing instance attributes are errors.

True
marker_file str | None

File used to detect whether an optional component exists. Set to None to always call the loader.

'config.json'
save_with_is_main_process bool

Whether to pass is_main_process to the component's save_pretrained method.

True
Source code in lib/laygen/src/laygen/pipelines/base.py
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
@dataclass(frozen=True)
class PipelineComponentSpec:
    """Declarative loading and saving rule for one pipeline component.

    Args:
        attribute_name: Attribute on the pipeline instance.
        loader: Loader callable used by `from_pretrained`.
        subfolder: Fixed subfolder under the checkpoint root. If omitted, the
            root itself is used.
        config_subfolder_attribute: Config attribute that stores the subfolder
            name. This takes precedence over `subfolder`.
        required: Whether missing marker files or missing instance attributes
            are errors.
        marker_file: File used to detect whether an optional component exists.
            Set to `None` to always call the loader.
        save_with_is_main_process: Whether to pass `is_main_process` to the
            component's `save_pretrained` method.
    """

    attribute_name: str
    loader: PipelineComponentLoader | None = None
    subfolder: str | None = None
    config_subfolder_attribute: str | None = None
    required: bool = True
    marker_file: str | None = "config.json"
    save_with_is_main_process: bool = True

    def component_path(
        self,
        root: Path,
        config: PretrainedConfig,
    ) -> Path:
        """Resolve the component path under a checkpoint root.

        Args:
            root: Checkpoint root directory.
            config: Root pipeline config.

        Returns:
            Resolved root or subfolder path.

        Raises:
            TypeError: If the configured subfolder attribute is not a string.
        """
        if self.config_subfolder_attribute is not None:
            value = getattr(config, self.config_subfolder_attribute)
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.config_subfolder_attribute} must be a string subfolder"
                )

            return root / value
        if self.subfolder is not None:
            return root / self.subfolder
        return root

    def component_subfolder(self, config: PretrainedConfig) -> str | None:
        """Resolve the component subfolder for Hub-backed loading.

        Args:
            config: Root pipeline config.

        Returns:
            Component subfolder or `None` when the component lives at the root.

        Raises:
            TypeError: If the configured subfolder attribute is not a string.
        """
        if self.config_subfolder_attribute is not None:
            value = getattr(config, self.config_subfolder_attribute)
            if not isinstance(value, str):
                raise TypeError(
                    f"{self.config_subfolder_attribute} must be a string subfolder"
                )

            return value
        return self.subfolder
component_path
component_path(
    root: Path, config: PretrainedConfig
) -> Path

Resolve the component path under a checkpoint root.

Parameters:

Name Type Description Default
root Path

Checkpoint root directory.

required
config PretrainedConfig

Root pipeline config.

required

Returns:

Type Description
Path

Resolved root or subfolder path.

Raises:

Type Description
TypeError

If the configured subfolder attribute is not a string.

Source code in lib/laygen/src/laygen/pipelines/base.py
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
def component_path(
    self,
    root: Path,
    config: PretrainedConfig,
) -> Path:
    """Resolve the component path under a checkpoint root.

    Args:
        root: Checkpoint root directory.
        config: Root pipeline config.

    Returns:
        Resolved root or subfolder path.

    Raises:
        TypeError: If the configured subfolder attribute is not a string.
    """
    if self.config_subfolder_attribute is not None:
        value = getattr(config, self.config_subfolder_attribute)
        if not isinstance(value, str):
            raise TypeError(
                f"{self.config_subfolder_attribute} must be a string subfolder"
            )

        return root / value
    if self.subfolder is not None:
        return root / self.subfolder
    return root
component_subfolder
component_subfolder(config: PretrainedConfig) -> str | None

Resolve the component subfolder for Hub-backed loading.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config.

required

Returns:

Type Description
str | None

Component subfolder or None when the component lives at the root.

Raises:

Type Description
TypeError

If the configured subfolder attribute is not a string.

Source code in lib/laygen/src/laygen/pipelines/base.py
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
def component_subfolder(self, config: PretrainedConfig) -> str | None:
    """Resolve the component subfolder for Hub-backed loading.

    Args:
        config: Root pipeline config.

    Returns:
        Component subfolder or `None` when the component lives at the root.

    Raises:
        TypeError: If the configured subfolder attribute is not a string.
    """
    if self.config_subfolder_attribute is not None:
        value = getattr(config, self.config_subfolder_attribute)
        if not isinstance(value, str):
            raise TypeError(
                f"{self.config_subfolder_attribute} must be a string subfolder"
            )

        return value
    return self.subfolder

LayoutGenerationPipeline

Bases: ABC

Base class for Transformers-side layout-generation pipelines.

Subclasses declare checkpoint components with component_specs, implement _from_pretrained_components, and put generation orchestration in __call__. The public __call__ contract is to return laygen.modeling_outputs.LayoutGenerationOutput for layout-generation outputs.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config, usually a PretrainedConfig subclass.

required

Examples:

>>> from transformers import PretrainedConfig
>>> class ToyPipeline(LayoutGenerationPipeline):
...     config_class = PretrainedConfig
...     @classmethod
...     def _from_pretrained_components(cls, *, config, components):
...         return cls(config)
...     def __call__(self):
...         import torch
...         return LayoutGenerationOutput(
...             bbox=torch.zeros(1, 1, 4),
...             labels=torch.zeros(1, 1, dtype=torch.long),
...             mask=torch.ones(1, 1, dtype=torch.bool),
...             id2label={0: "text"},
...         )
>>> isinstance(ToyPipeline(PretrainedConfig()).config, PretrainedConfig)
True
Source code in lib/laygen/src/laygen/pipelines/base.py
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
class LayoutGenerationPipeline(ABC):
    """Base class for Transformers-side layout-generation pipelines.

    Subclasses declare checkpoint components with `component_specs`, implement
    `_from_pretrained_components`, and put generation orchestration in
    `__call__`. The public `__call__` contract is to return
    `laygen.modeling_outputs.LayoutGenerationOutput` for layout-generation
    outputs.

    Args:
        config: Root pipeline config, usually a `PretrainedConfig` subclass.

    Examples:
        >>> from transformers import PretrainedConfig
        >>> class ToyPipeline(LayoutGenerationPipeline):
        ...     config_class = PretrainedConfig
        ...     @classmethod
        ...     def _from_pretrained_components(cls, *, config, components):
        ...         return cls(config)
        ...     def __call__(self):
        ...         import torch
        ...         return LayoutGenerationOutput(
        ...             bbox=torch.zeros(1, 1, 4),
        ...             labels=torch.zeros(1, 1, dtype=torch.long),
        ...             mask=torch.ones(1, 1, dtype=torch.bool),
        ...             id2label={0: "text"},
        ...         )
        >>> isinstance(ToyPipeline(PretrainedConfig()).config, PretrainedConfig)
        True
    """

    config_class: ClassVar[type[PretrainedConfig]] = PretrainedConfig
    component_specs: ClassVar[Mapping[str, PipelineComponentSpec]] = {}

    config: PretrainedConfig
    device: torch.device | None
    dtype: torch.dtype | None

    def __init__(self, config: PretrainedConfig) -> None:
        """Initialize root config and runtime placement metadata.

        Args:
            config: Root pipeline config.
        """
        self.config = config
        self.device = None
        self.dtype = None

    @classmethod
    def from_pretrained(
        cls,
        pretrained_model_name_or_path: str | Path,
        *,
        local_files_only: bool = False,
        config: PretrainedConfig | None = None,
        components: Mapping[str, PipelineComponent] | None = None,
    ) -> Self:
        """Load a pipeline from a checkpoint root and declared subfolders.

        Args:
            pretrained_model_name_or_path: Checkpoint root.
            local_files_only: Whether to avoid network access.
            config: Optional preloaded root config.
            components: Optional preloaded components keyed by spec name.

        Returns:
            Loaded pipeline instance.

        Raises:
            FileNotFoundError: If a required component marker file is missing.
            TypeError: If `config` is not compatible with `config_class`.
        """
        root = Path(pretrained_model_name_or_path)
        source = pretrained_model_name_or_path
        pipeline_config = cls._load_pipeline_config(
            source,
            local_files_only=local_files_only,
            config=config,
        )
        loaded_components = cls._load_pipeline_components(
            root,
            source,
            pipeline_config,
            local_files_only=local_files_only,
            components=components or {},
        )
        return cls._from_pretrained_components(
            config=pipeline_config,
            components=loaded_components,
        )

    @classmethod
    def _load_pipeline_config(
        cls,
        source: str | Path,
        *,
        local_files_only: bool,
        config: PretrainedConfig | None,
    ) -> PretrainedConfig:
        if config is None:
            return cls.config_class.from_pretrained(
                source,
                local_files_only=local_files_only,
            )
        if isinstance(config, cls.config_class):
            return config
        if isinstance(config, PretrainedConfig):
            return cls.config_class.from_dict(config.to_dict())
        raise TypeError(f"config must be a {cls.config_class.__name__}")

    @classmethod
    def _load_pipeline_components(
        cls,
        root: Path,
        source: str | Path,
        config: PretrainedConfig,
        *,
        local_files_only: bool,
        components: Mapping[str, PipelineComponent],
    ) -> dict[str, PipelineComponent | None]:
        loaded: dict[str, PipelineComponent | None] = {}
        for name, spec in cls.component_specs.items():
            if name in components:
                loaded[name] = components[name]
                continue
            if spec.loader is None:
                loaded[name] = None
                continue
            if root.is_dir():
                component_path = spec.component_path(root, config)
                marker = (
                    component_path / spec.marker_file
                    if spec.marker_file is not None
                    else None
                )
                if marker is not None and not marker.exists():
                    if spec.required:
                        raise FileNotFoundError(
                            f"Required pipeline component '{name}' is missing: {marker}"
                        )

                    loaded[name] = None
                    continue
                loaded[name] = spec.loader(
                    component_path,
                    local_files_only=local_files_only,
                )
                continue
            loaded[name] = spec.loader(
                source,
                local_files_only=local_files_only,
                subfolder=spec.component_subfolder(config),
            )
        return loaded

    @classmethod
    @abstractmethod
    def _from_pretrained_components(
        cls,
        *,
        config: PretrainedConfig,
        components: Mapping[str, PipelineComponent | None],
    ) -> Self:
        """Build a pipeline from a root config and loaded components.

        Args:
            config: Loaded root config.
            components: Components keyed by `component_specs` names.

        Returns:
            Loaded pipeline instance.
        """

    def save_pretrained(
        self,
        save_directory: str | Path,
        *,
        is_main_process: bool = True,
    ) -> None:
        """Save root config and declared components.

        Args:
            save_directory: Checkpoint root directory.
            is_main_process: Whether model-like components should perform main
                process writes.

        Raises:
            TypeError: If a component does not implement `save_pretrained`.
            ValueError: If a required component attribute is missing.
        """
        root = Path(save_directory)
        root.mkdir(parents=True, exist_ok=True)
        self.config.save_pretrained(root)
        for name, spec in self.component_specs.items():
            component = getattr(self, spec.attribute_name, None)
            if component is None:
                if spec.required:
                    raise ValueError(f"Required pipeline component '{name}' is None")

                continue
            component_path = spec.component_path(root, self.config)
            component_path.mkdir(parents=True, exist_ok=True)
            if spec.save_with_is_main_process:
                if not isinstance(component, SavePretrainedWithMainProcess):
                    raise TypeError(
                        f"Pipeline component '{name}' does not support save_pretrained"
                    )

                component.save_pretrained(
                    component_path,
                    is_main_process=is_main_process,
                )
            else:
                if not isinstance(component, SavePretrainedPlain):
                    raise TypeError(
                        f"Pipeline component '{name}' does not support save_pretrained"
                    )

                component.save_pretrained(component_path)

    def to(
        self,
        device: str | torch.device | None = None,
        dtype: torch.dtype | None = None,
    ) -> Self:
        """Move movable components to a device and/or dtype.

        Args:
            device: Target torch device.
            dtype: Target torch dtype.

        Returns:
            This pipeline instance.
        """
        if device is not None:
            self.device = torch.device(device)
        if dtype is not None:
            self.dtype = dtype
        if self.device is None and self.dtype is None:
            return self
        for component in self._pipeline_component_values():
            if isinstance(component, TorchMovable):
                component.to(device=self.device, dtype=self.dtype)
        return self

    def prepare_generator(
        self,
        *,
        generator: torch.Generator | None = None,
        seed: int | None = None,
        device: str | torch.device | None = None,
    ) -> torch.Generator | None:
        """Apply generator-over-seed precedence for generation calls.

        Args:
            generator: Explicit torch generator. When provided, `seed` is
                ignored.
            seed: Integer seed used only when `generator` is absent.
            device: Optional device for a newly created generator. If omitted,
                the pipeline's current device is used.

        Returns:
            The explicit generator, a seeded generator when a device is known,
            or `None` after setting global Transformers/PyTorch seed state.
        """
        if generator is not None:
            return generator
        if seed is None:
            return None
        set_seed(seed)
        generator_device = torch.device(device) if device is not None else self.device
        if generator_device is None:
            return None
        return torch.Generator(device=generator_device).manual_seed(seed)

    def _pipeline_component_values(self) -> tuple[PipelineComponent, ...]:
        values: list[PipelineComponent] = []
        for spec in self.component_specs.values():
            component = getattr(self, spec.attribute_name, None)
            if component is not None:
                values.append(cast(PipelineComponent, component))
        return tuple(values)

    @abstractmethod
    def __call__(self) -> LayoutGenerationOutput:
        """Generate a layout.

        Returns:
            Layout generation output in the canonical Transformers-style schema.
        """
__init__
__init__(config: PretrainedConfig) -> None

Initialize root config and runtime placement metadata.

Parameters:

Name Type Description Default
config PretrainedConfig

Root pipeline config.

required
Source code in lib/laygen/src/laygen/pipelines/base.py
249
250
251
252
253
254
255
256
257
def __init__(self, config: PretrainedConfig) -> None:
    """Initialize root config and runtime placement metadata.

    Args:
        config: Root pipeline config.
    """
    self.config = config
    self.device = None
    self.dtype = None
from_pretrained classmethod
from_pretrained(
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    config: PretrainedConfig | None = None,
    components: Mapping[str, PipelineComponent]
    | None = None,
) -> Self

Load a pipeline from a checkpoint root and declared subfolders.

Parameters:

Name Type Description Default
pretrained_model_name_or_path str | Path

Checkpoint root.

required
local_files_only bool

Whether to avoid network access.

False
config PretrainedConfig | None

Optional preloaded root config.

None
components Mapping[str, PipelineComponent] | None

Optional preloaded components keyed by spec name.

None

Returns:

Type Description
Self

Loaded pipeline instance.

Raises:

Type Description
FileNotFoundError

If a required component marker file is missing.

TypeError

If config is not compatible with config_class.

Source code in lib/laygen/src/laygen/pipelines/base.py
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
@classmethod
def from_pretrained(
    cls,
    pretrained_model_name_or_path: str | Path,
    *,
    local_files_only: bool = False,
    config: PretrainedConfig | None = None,
    components: Mapping[str, PipelineComponent] | None = None,
) -> Self:
    """Load a pipeline from a checkpoint root and declared subfolders.

    Args:
        pretrained_model_name_or_path: Checkpoint root.
        local_files_only: Whether to avoid network access.
        config: Optional preloaded root config.
        components: Optional preloaded components keyed by spec name.

    Returns:
        Loaded pipeline instance.

    Raises:
        FileNotFoundError: If a required component marker file is missing.
        TypeError: If `config` is not compatible with `config_class`.
    """
    root = Path(pretrained_model_name_or_path)
    source = pretrained_model_name_or_path
    pipeline_config = cls._load_pipeline_config(
        source,
        local_files_only=local_files_only,
        config=config,
    )
    loaded_components = cls._load_pipeline_components(
        root,
        source,
        pipeline_config,
        local_files_only=local_files_only,
        components=components or {},
    )
    return cls._from_pretrained_components(
        config=pipeline_config,
        components=loaded_components,
    )
save_pretrained
save_pretrained(
    save_directory: str | Path,
    *,
    is_main_process: bool = True,
) -> None

Save root config and declared components.

Parameters:

Name Type Description Default
save_directory str | Path

Checkpoint root directory.

required
is_main_process bool

Whether model-like components should perform main process writes.

True

Raises:

Type Description
TypeError

If a component does not implement save_pretrained.

ValueError

If a required component attribute is missing.

Source code in lib/laygen/src/laygen/pipelines/base.py
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
def save_pretrained(
    self,
    save_directory: str | Path,
    *,
    is_main_process: bool = True,
) -> None:
    """Save root config and declared components.

    Args:
        save_directory: Checkpoint root directory.
        is_main_process: Whether model-like components should perform main
            process writes.

    Raises:
        TypeError: If a component does not implement `save_pretrained`.
        ValueError: If a required component attribute is missing.
    """
    root = Path(save_directory)
    root.mkdir(parents=True, exist_ok=True)
    self.config.save_pretrained(root)
    for name, spec in self.component_specs.items():
        component = getattr(self, spec.attribute_name, None)
        if component is None:
            if spec.required:
                raise ValueError(f"Required pipeline component '{name}' is None")

            continue
        component_path = spec.component_path(root, self.config)
        component_path.mkdir(parents=True, exist_ok=True)
        if spec.save_with_is_main_process:
            if not isinstance(component, SavePretrainedWithMainProcess):
                raise TypeError(
                    f"Pipeline component '{name}' does not support save_pretrained"
                )

            component.save_pretrained(
                component_path,
                is_main_process=is_main_process,
            )
        else:
            if not isinstance(component, SavePretrainedPlain):
                raise TypeError(
                    f"Pipeline component '{name}' does not support save_pretrained"
                )

            component.save_pretrained(component_path)
to
to(
    device: str | device | None = None,
    dtype: dtype | None = None,
) -> Self

Move movable components to a device and/or dtype.

Parameters:

Name Type Description Default
device str | device | None

Target torch device.

None
dtype dtype | None

Target torch dtype.

None

Returns:

Type Description
Self

This pipeline instance.

Source code in lib/laygen/src/laygen/pipelines/base.py
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
def to(
    self,
    device: str | torch.device | None = None,
    dtype: torch.dtype | None = None,
) -> Self:
    """Move movable components to a device and/or dtype.

    Args:
        device: Target torch device.
        dtype: Target torch dtype.

    Returns:
        This pipeline instance.
    """
    if device is not None:
        self.device = torch.device(device)
    if dtype is not None:
        self.dtype = dtype
    if self.device is None and self.dtype is None:
        return self
    for component in self._pipeline_component_values():
        if isinstance(component, TorchMovable):
            component.to(device=self.device, dtype=self.dtype)
    return self
prepare_generator
prepare_generator(
    *,
    generator: Generator | None = None,
    seed: int | None = None,
    device: str | device | None = None,
) -> torch.Generator | None

Apply generator-over-seed precedence for generation calls.

Parameters:

Name Type Description Default
generator Generator | None

Explicit torch generator. When provided, seed is ignored.

None
seed int | None

Integer seed used only when generator is absent.

None
device str | device | None

Optional device for a newly created generator. If omitted, the pipeline's current device is used.

None

Returns:

Type Description
Generator | None

The explicit generator, a seeded generator when a device is known,

Generator | None

or None after setting global Transformers/PyTorch seed state.

Source code in lib/laygen/src/laygen/pipelines/base.py
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
def prepare_generator(
    self,
    *,
    generator: torch.Generator | None = None,
    seed: int | None = None,
    device: str | torch.device | None = None,
) -> torch.Generator | None:
    """Apply generator-over-seed precedence for generation calls.

    Args:
        generator: Explicit torch generator. When provided, `seed` is
            ignored.
        seed: Integer seed used only when `generator` is absent.
        device: Optional device for a newly created generator. If omitted,
            the pipeline's current device is used.

    Returns:
        The explicit generator, a seeded generator when a device is known,
        or `None` after setting global Transformers/PyTorch seed state.
    """
    if generator is not None:
        return generator
    if seed is None:
        return None
    set_seed(seed)
    generator_device = torch.device(device) if device is not None else self.device
    if generator_device is None:
        return None
    return torch.Generator(device=generator_device).manual_seed(seed)
__call__ abstractmethod
__call__() -> LayoutGenerationOutput

Generate a layout.

Returns:

Type Description
LayoutGenerationOutput

Layout generation output in the canonical Transformers-style schema.

Source code in lib/laygen/src/laygen/pipelines/base.py
494
495
496
497
498
499
500
@abstractmethod
def __call__(self) -> LayoutGenerationOutput:
    """Generate a layout.

    Returns:
        Layout generation output in the canonical Transformers-style schema.
    """

model_processor_component_specs

model_processor_component_specs(
    *,
    model_loader: PipelineComponentLoader,
    processor_loader: PipelineComponentLoader,
) -> dict[str, PipelineComponentSpec]

Build standard model/processor component specs for simple pipelines.

Source code in lib/laygen/src/laygen/pipelines/base.py
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
def model_processor_component_specs(
    *,
    model_loader: PipelineComponentLoader,
    processor_loader: PipelineComponentLoader,
) -> dict[str, PipelineComponentSpec]:
    """Build standard model/processor component specs for simple pipelines."""
    return {
        "model": PipelineComponentSpec(
            attribute_name="model",
            loader=model_loader,
            marker_file="config.json",
        ),
        "processor": PipelineComponentSpec(
            attribute_name="processor",
            loader=processor_loader,
            marker_file="processor_config.json",
            save_with_is_main_process=False,
        ),
    }

pipeline_output

Diffusers-compatible output types for layout generation pipelines.

LayoutGenerationOutput dataclass

Bases: BaseOutput

Layout-generation output for Diffusers pipelines.

Attributes:

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

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

labels Int[Tensor, 'batch elements']

Dataset-local integer labels with shape (batch, elements).

mask Bool[Tensor, 'batch elements']

Boolean valid-element mask with shape (batch, elements).

id2label dict[int, str]

Mapping from integer label ids to display names.

sequences object | None

Optional raw token sequences.

scores object | None

Optional per-token or per-element scores.

trajectory object | None

Optional sampling trajectory.

intermediates object | None

Optional model-specific debug or auxiliary data.

Examples:

>>> import torch
>>> output = LayoutGenerationOutput(
...     bbox=torch.zeros(1, 1, 4),
...     labels=torch.zeros(1, 1, dtype=torch.long),
...     mask=torch.ones(1, 1, dtype=torch.bool),
...     id2label={0: "text"},
... )
>>> output.to_tuple()[0].shape
torch.Size([1, 1, 4])
Source code in lib/laygen/src/laygen/pipelines/pipeline_output.py
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@dataclass
class LayoutGenerationOutput(BaseOutput):
    """Layout-generation output for Diffusers pipelines.

    Attributes:
        bbox: Normalized center ``xywh`` boxes with shape
            ``(batch, elements, 4)``.
        labels: Dataset-local integer labels with shape ``(batch, elements)``.
        mask: Boolean valid-element mask with shape ``(batch, elements)``.
        id2label: Mapping from integer label ids to display names.
        sequences: Optional raw token sequences.
        scores: Optional per-token or per-element scores.
        trajectory: Optional sampling trajectory.
        intermediates: Optional model-specific debug or auxiliary data.

    Examples:
        >>> import torch
        >>> output = LayoutGenerationOutput(
        ...     bbox=torch.zeros(1, 1, 4),
        ...     labels=torch.zeros(1, 1, dtype=torch.long),
        ...     mask=torch.ones(1, 1, dtype=torch.bool),
        ...     id2label={0: "text"},
        ... )
        >>> output.to_tuple()[0].shape
        torch.Size([1, 1, 4])
    """

    bbox: Float[torch.Tensor, "batch elements 4"]
    labels: Int[torch.Tensor, "batch elements"] = cast(
        'Int[torch.Tensor, "batch elements"]', None
    )
    mask: Bool[torch.Tensor, "batch elements"] = cast(
        'Bool[torch.Tensor, "batch elements"]', None
    )
    id2label: dict[int, str] = cast(dict[int, str], None)
    sequences: object | None = None
    scores: object | None = None
    trajectory: object | None = None
    intermediates: object | None = None

schedulers

Shared scheduler helpers for layout generation models.

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()

get_beta_schedule

get_beta_schedule(
    schedule: BetaSchedule | str = BetaSchedule.cosine,
    num_timesteps: int = 1000,
    start: float = 0.0001,
    end: float = 0.02,
) -> Float[torch.Tensor, "timesteps"]

Create a beta schedule, delegating common schedules to Diffusers.

Origin

The public API follows CompVis latent-diffusion make_beta_schedule. Common DDPM schedules delegate to Diffusers DDPMScheduler while legacy LACE-only aliases remain custom for compatibility.

Parameters:

Name Type Description Default
schedule BetaSchedule | str

Schedule enum or string value.

cosine
num_timesteps int

Number of training timesteps.

1000
start float

Initial beta value for schedules that use a range.

0.0001
end float

Final beta value for schedules that use a range.

0.02

Returns:

Type Description
Float[Tensor, 'timesteps']

One-dimensional beta tensor.

Raises:

Type Description
ValueError

If the schedule is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
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
def get_beta_schedule(
    schedule: BetaSchedule | str = BetaSchedule.cosine,
    num_timesteps: int = 1000,
    start: float = 0.0001,
    end: float = 0.02,
) -> Float[torch.Tensor, "timesteps"]:
    """Create a beta schedule, delegating common schedules to Diffusers.

    Origin:
        The public API follows CompVis latent-diffusion ``make_beta_schedule``.
        Common DDPM schedules delegate to Diffusers ``DDPMScheduler`` while
        legacy LACE-only aliases remain custom for compatibility.

    Args:
        schedule: Schedule enum or string value.
        num_timesteps: Number of training timesteps.
        start: Initial beta value for schedules that use a range.
        end: Final beta value for schedules that use a range.

    Returns:
        One-dimensional beta tensor.

    Raises:
        ValueError: If the schedule is unsupported.
    """
    canonical = normalize_beta_schedule(schedule)
    diffusers_schedule = _diffusers_beta_schedule(canonical)
    if diffusers_schedule is not None:
        return DDPMScheduler(
            num_train_timesteps=num_timesteps,
            beta_start=start,
            beta_end=end,
            beta_schedule=diffusers_schedule,
        ).betas
    if canonical is BetaSchedule.const:
        return end * torch.ones(num_timesteps)
    if canonical is BetaSchedule.jsd:
        return 1.0 / torch.linspace(num_timesteps, 1, num_timesteps)
    if canonical is BetaSchedule.cosine_anneal:
        return torch.tensor(
            [
                start
                + 0.5
                * (end - start)
                * (1 - math.cos(t / (num_timesteps - 1) * math.pi))
                for t in range(num_timesteps)
            ]
        )
    raise ValueError(f"Unsupported beta schedule: {schedule}")

get_ddim_timesteps

get_ddim_timesteps(
    method: DDIMDiscretization | str,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    *,
    steps_offset: int = 1,
) -> Int[np.ndarray, "ddim_timesteps"]

Create ascending reference-order DDIM timesteps.

Origin

The public API follows CompVis latent-diffusion make_ddim_timesteps. The uniform branch adapts Diffusers DDIMScheduler back to LACE's ascending one-indexed reference order.

Parameters:

Name Type Description Default
method DDIMDiscretization | str

Discretization enum or string value.

required
num_ddim_timesteps int

Number of inference timesteps.

required
num_ddpm_timesteps int

Number of training timesteps.

required
steps_offset int

Diffusers timestep offset. LACE uses one-indexed timesteps, so the default is 1.

1

Returns:

Type Description
Int[ndarray, 'ddim_timesteps']

NumPy array of timesteps in ascending reference order.

Raises:

Type Description
ValueError

If the method is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def get_ddim_timesteps(
    method: DDIMDiscretization | str,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    *,
    steps_offset: int = 1,
) -> Int[np.ndarray, "ddim_timesteps"]:
    """Create ascending reference-order DDIM timesteps.

    Origin:
        The public API follows CompVis latent-diffusion ``make_ddim_timesteps``.
        The uniform branch adapts Diffusers ``DDIMScheduler`` back to LACE's
        ascending one-indexed reference order.

    Args:
        method: Discretization enum or string value.
        num_ddim_timesteps: Number of inference timesteps.
        num_ddpm_timesteps: Number of training timesteps.
        steps_offset: Diffusers timestep offset. LACE uses one-indexed
            timesteps, so the default is ``1``.

    Returns:
        NumPy array of timesteps in ascending reference order.

    Raises:
        ValueError: If the method is unsupported.
    """
    canonical = normalize_ddim_discretization(method)
    if canonical is DDIMDiscretization.uniform:
        scheduler = DDIMScheduler(
            num_train_timesteps=num_ddpm_timesteps,
            timestep_spacing="leading",
            steps_offset=steps_offset,
        )
        scheduler.set_timesteps(num_ddim_timesteps)
        return scheduler.timesteps.cpu().numpy()[::-1].copy()
    if canonical is DDIMDiscretization.quad:
        timesteps = (
            np.linspace(0, np.sqrt(num_ddpm_timesteps * 0.8), num_ddim_timesteps) ** 2
        ).astype(int)
        return timesteps + steps_offset
    if canonical is DDIMDiscretization.new:
        c = (num_ddpm_timesteps - 50) // (num_ddim_timesteps - 50)
        timesteps = np.asarray(
            list(range(0, 50)) + list(range(50, num_ddpm_timesteps - 50, c))
        )
        return timesteps + steps_offset
    assert_never(canonical)

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

continuous

Continuous diffusion scheduler adapters backed by Diffusers.

The beta/DDIM helper names follow CompVis latent-diffusion ldm/modules/diffusionmodules/util.py utilities as used by the LACE diffusion_utils.py. Common schedules are delegated to Diffusers schedulers.

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()

LayoutDiffusionBetaSchedule

Bases: StrEnum

LayoutDiffusion-specific beta schedules.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
40
41
42
43
44
45
46
47
class LayoutDiffusionBetaSchedule(StrEnum):
    """LayoutDiffusion-specific beta schedules."""

    sqrt = auto()
    mix_sqrt = auto()
    trunc_cos = auto()
    trunc_lin = auto()
    pw_lin = 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()

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_layoutdiffusion_beta_schedule

normalize_layoutdiffusion_beta_schedule(
    schedule: LayoutDiffusionBetaSchedule | str,
) -> LayoutDiffusionBetaSchedule

Normalize a LayoutDiffusion-only beta schedule name.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
87
88
89
90
91
92
93
94
95
96
97
98
def normalize_layoutdiffusion_beta_schedule(
    schedule: LayoutDiffusionBetaSchedule | str,
) -> LayoutDiffusionBetaSchedule:
    """Normalize a LayoutDiffusion-only beta schedule name."""
    if isinstance(schedule, LayoutDiffusionBetaSchedule):
        return schedule
    try:
        return LayoutDiffusionBetaSchedule(schedule)
    except ValueError as exc:
        raise ValueError(
            f"Unsupported LayoutDiffusion 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

get_layoutdiffusion_beta_schedule

get_layoutdiffusion_beta_schedule(
    schedule: LayoutDiffusionBetaSchedule | str,
    num_timesteps: int,
) -> Float[torch.Tensor, "timesteps"]

Create LayoutDiffusion-specific beta schedules.

Origin

These formulas are copied narrowly from LayoutDiffusion's vendored OpenAI improved_diffusion.gaussian_diffusion.get_named_beta_schedule branches for names not exposed by Diffusers.

Parameters:

Name Type Description Default
schedule LayoutDiffusionBetaSchedule | str

LayoutDiffusion schedule enum or string value.

required
num_timesteps int

Number of diffusion timesteps.

required

Returns:

Type Description
Float[Tensor, 'timesteps']

Float64 beta tensor matching the reference NumPy formula.

Raises:

Type Description
ValueError

If schedule is not a LayoutDiffusion-only schedule.

Examples:

>>> get_layoutdiffusion_beta_schedule("sqrt", 4).shape
torch.Size([4])
Source code in lib/laygen/src/laygen/schedulers/continuous.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
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
def get_layoutdiffusion_beta_schedule(
    schedule: LayoutDiffusionBetaSchedule | str,
    num_timesteps: int,
) -> Float[torch.Tensor, "timesteps"]:
    """Create LayoutDiffusion-specific beta schedules.

    Origin:
        These formulas are copied narrowly from LayoutDiffusion's vendored
        OpenAI ``improved_diffusion.gaussian_diffusion.get_named_beta_schedule``
        branches for names not exposed by Diffusers.

    Args:
        schedule: LayoutDiffusion schedule enum or string value.
        num_timesteps: Number of diffusion timesteps.

    Returns:
        Float64 beta tensor matching the reference NumPy formula.

    Raises:
        ValueError: If ``schedule`` is not a LayoutDiffusion-only schedule.

    Examples:
        >>> get_layoutdiffusion_beta_schedule("sqrt", 4).shape
        torch.Size([4])
    """
    canonical = normalize_layoutdiffusion_beta_schedule(schedule)
    if canonical is LayoutDiffusionBetaSchedule.sqrt:
        return _betas_for_alpha_bar(num_timesteps, lambda t: 1 - np.sqrt(t + 0.0001))
    if canonical is LayoutDiffusionBetaSchedule.mix_sqrt:
        return _betas_for_alpha_bar(
            num_timesteps,
            lambda t: (
                (1 - np.cbrt(t / 2.0 + 0.000001))
                if t < 0.5
                else min(
                    1 - np.sqrt(2.0 * t - 1.0 + 0.0001),
                    1 - np.cbrt(t / 2.0 + 0.000001),
                )
            ),
        )
    if canonical is LayoutDiffusionBetaSchedule.trunc_cos:
        return _betas_for_alpha_bar(
            num_timesteps,
            lambda t: np.cos((t + 0.1) / 1.1 * np.pi / 2) ** 2,
            include_initial=True,
        )
    if canonical is LayoutDiffusionBetaSchedule.trunc_lin:
        scale = 1000 / num_timesteps
        return torch.from_numpy(
            np.linspace(
                scale * 0.0001 + 0.01,
                scale * 0.02 + 0.01,
                num_timesteps,
                dtype=np.float64,
            )
        )
    if canonical is LayoutDiffusionBetaSchedule.pw_lin:
        scale = 1000 / num_timesteps
        first_part = np.linspace(
            scale * 0.0001 + 0.01,
            scale * 0.0001,
            10,
            dtype=np.float64,
        )
        second_part = np.linspace(
            scale * 0.0001,
            scale * 0.02,
            num_timesteps - 10,
            dtype=np.float64,
        )
        return torch.from_numpy(np.concatenate([first_part, second_part]))
    raise ValueError(f"Unsupported LayoutDiffusion beta schedule: {schedule}")

get_beta_schedule

get_beta_schedule(
    schedule: BetaSchedule | str = BetaSchedule.cosine,
    num_timesteps: int = 1000,
    start: float = 0.0001,
    end: float = 0.02,
) -> Float[torch.Tensor, "timesteps"]

Create a beta schedule, delegating common schedules to Diffusers.

Origin

The public API follows CompVis latent-diffusion make_beta_schedule. Common DDPM schedules delegate to Diffusers DDPMScheduler while legacy LACE-only aliases remain custom for compatibility.

Parameters:

Name Type Description Default
schedule BetaSchedule | str

Schedule enum or string value.

cosine
num_timesteps int

Number of training timesteps.

1000
start float

Initial beta value for schedules that use a range.

0.0001
end float

Final beta value for schedules that use a range.

0.02

Returns:

Type Description
Float[Tensor, 'timesteps']

One-dimensional beta tensor.

Raises:

Type Description
ValueError

If the schedule is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
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
def get_beta_schedule(
    schedule: BetaSchedule | str = BetaSchedule.cosine,
    num_timesteps: int = 1000,
    start: float = 0.0001,
    end: float = 0.02,
) -> Float[torch.Tensor, "timesteps"]:
    """Create a beta schedule, delegating common schedules to Diffusers.

    Origin:
        The public API follows CompVis latent-diffusion ``make_beta_schedule``.
        Common DDPM schedules delegate to Diffusers ``DDPMScheduler`` while
        legacy LACE-only aliases remain custom for compatibility.

    Args:
        schedule: Schedule enum or string value.
        num_timesteps: Number of training timesteps.
        start: Initial beta value for schedules that use a range.
        end: Final beta value for schedules that use a range.

    Returns:
        One-dimensional beta tensor.

    Raises:
        ValueError: If the schedule is unsupported.
    """
    canonical = normalize_beta_schedule(schedule)
    diffusers_schedule = _diffusers_beta_schedule(canonical)
    if diffusers_schedule is not None:
        return DDPMScheduler(
            num_train_timesteps=num_timesteps,
            beta_start=start,
            beta_end=end,
            beta_schedule=diffusers_schedule,
        ).betas
    if canonical is BetaSchedule.const:
        return end * torch.ones(num_timesteps)
    if canonical is BetaSchedule.jsd:
        return 1.0 / torch.linspace(num_timesteps, 1, num_timesteps)
    if canonical is BetaSchedule.cosine_anneal:
        return torch.tensor(
            [
                start
                + 0.5
                * (end - start)
                * (1 - math.cos(t / (num_timesteps - 1) * math.pi))
                for t in range(num_timesteps)
            ]
        )
    raise ValueError(f"Unsupported beta schedule: {schedule}")

get_layousyn_beta_schedule

get_layousyn_beta_schedule(
    schedule: Literal[
        "linear", "squaredcos_cap_v2"
    ] = "linear",
    num_timesteps: int = 100,
    *,
    alpha_scale: float = 1.0,
) -> Float[torch.Tensor, "timesteps"]

Create LayouSyn/OpenAI-style beta schedules.

Origin

This preserves the LayouSyn gaussian_diffusion.py formula exactly, including the LayYourScene-specific alpha_scale transform for both the linear and squared-cosine schedules.

Parameters:

Name Type Description Default
schedule Literal['linear', 'squaredcos_cap_v2']

Schedule name.

'linear'
num_timesteps int

Number of diffusion timesteps.

100
alpha_scale float

Alpha-bar scaling factor.

1.0

Returns:

Type Description
Float[Tensor, 'timesteps']

One-dimensional beta tensor in float64 precision.

Raises:

Type Description
ValueError

If the schedule is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
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
def get_layousyn_beta_schedule(
    schedule: Literal["linear", "squaredcos_cap_v2"] = "linear",
    num_timesteps: int = 100,
    *,
    alpha_scale: float = 1.0,
) -> Float[torch.Tensor, "timesteps"]:
    """Create LayouSyn/OpenAI-style beta schedules.

    Origin:
        This preserves the LayouSyn ``gaussian_diffusion.py`` formula exactly,
        including the LayYourScene-specific
        ``alpha_scale`` transform for both the linear and squared-cosine
        schedules.

    Args:
        schedule: Schedule name.
        num_timesteps: Number of diffusion timesteps.
        alpha_scale: Alpha-bar scaling factor.

    Returns:
        One-dimensional beta tensor in float64 precision.

    Raises:
        ValueError: If the schedule is unsupported.
    """
    if schedule == "linear":
        scale = 1000 / num_timesteps
        betas_np = np.linspace(
            scale * 0.0001,
            scale * 0.02,
            num_timesteps,
            dtype=np.float64,
        )
        if alpha_scale == 1.0:
            return torch.from_numpy(betas_np)
        alpha_cumprod = np.cumprod(1 - betas_np)
        alpha_scaled = (alpha_scale**2 * alpha_cumprod) / (
            (alpha_scale**2 - 1) * alpha_cumprod + 1.0
        )
        betas = [1 - alpha_scaled[0]]
        for i in range(1, num_timesteps):
            betas.append(1 - alpha_scaled[i] / alpha_scaled[i - 1])
        return torch.from_numpy(np.array(betas))
    if schedule == "squaredcos_cap_v2":
        betas = []
        for i in range(num_timesteps):
            t1 = i / num_timesteps
            t2 = (i + 1) / num_timesteps
            alpha_1 = _layousyn_scaled_cosine_alpha_bar(t1, alpha_scale)
            alpha_2 = _layousyn_scaled_cosine_alpha_bar(t2, alpha_scale)
            betas.append(min(1 - alpha_2 / alpha_1, 0.999))
        return torch.from_numpy(np.array(betas))
    raise ValueError(f"Unsupported LayouSyn beta schedule: {schedule}")

get_ddim_timesteps

get_ddim_timesteps(
    method: DDIMDiscretization | str,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    *,
    steps_offset: int = 1,
) -> Int[np.ndarray, "ddim_timesteps"]

Create ascending reference-order DDIM timesteps.

Origin

The public API follows CompVis latent-diffusion make_ddim_timesteps. The uniform branch adapts Diffusers DDIMScheduler back to LACE's ascending one-indexed reference order.

Parameters:

Name Type Description Default
method DDIMDiscretization | str

Discretization enum or string value.

required
num_ddim_timesteps int

Number of inference timesteps.

required
num_ddpm_timesteps int

Number of training timesteps.

required
steps_offset int

Diffusers timestep offset. LACE uses one-indexed timesteps, so the default is 1.

1

Returns:

Type Description
Int[ndarray, 'ddim_timesteps']

NumPy array of timesteps in ascending reference order.

Raises:

Type Description
ValueError

If the method is unsupported.

Source code in lib/laygen/src/laygen/schedulers/continuous.py
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
def get_ddim_timesteps(
    method: DDIMDiscretization | str,
    num_ddim_timesteps: int,
    num_ddpm_timesteps: int,
    *,
    steps_offset: int = 1,
) -> Int[np.ndarray, "ddim_timesteps"]:
    """Create ascending reference-order DDIM timesteps.

    Origin:
        The public API follows CompVis latent-diffusion ``make_ddim_timesteps``.
        The uniform branch adapts Diffusers ``DDIMScheduler`` back to LACE's
        ascending one-indexed reference order.

    Args:
        method: Discretization enum or string value.
        num_ddim_timesteps: Number of inference timesteps.
        num_ddpm_timesteps: Number of training timesteps.
        steps_offset: Diffusers timestep offset. LACE uses one-indexed
            timesteps, so the default is ``1``.

    Returns:
        NumPy array of timesteps in ascending reference order.

    Raises:
        ValueError: If the method is unsupported.
    """
    canonical = normalize_ddim_discretization(method)
    if canonical is DDIMDiscretization.uniform:
        scheduler = DDIMScheduler(
            num_train_timesteps=num_ddpm_timesteps,
            timestep_spacing="leading",
            steps_offset=steps_offset,
        )
        scheduler.set_timesteps(num_ddim_timesteps)
        return scheduler.timesteps.cpu().numpy()[::-1].copy()
    if canonical is DDIMDiscretization.quad:
        timesteps = (
            np.linspace(0, np.sqrt(num_ddpm_timesteps * 0.8), num_ddim_timesteps) ** 2
        ).astype(int)
        return timesteps + steps_offset
    if canonical is DDIMDiscretization.new:
        c = (num_ddpm_timesteps - 50) // (num_ddim_timesteps - 50)
        timesteps = np.asarray(
            list(range(0, 50)) + list(range(50, num_ddpm_timesteps - 50, c))
        )
        return timesteps + steps_offset
    assert_never(canonical)