Skip to content

Shared library architecture

The repository keeps reusable layout and poster-generation code in shared workspace libraries, shared repository-checker mechanics in tooling packages, and model-specific behavior in model packages.

Workspace layout

A workspace member is a package included in the root uv workspace, so contributors can develop it alongside the model packages that consume it.

The workspace contains shared libraries under lib/*, model packages under models/*, and repository-development tooling packages under tools/*.

lib/
  laygen/
  posgen/
  traingen/
  traingen-parity/
models/
  <model-package>/
tools/
  devharness/

The root workspace declaration is:

[tool.uv.workspace]
members = ["lib/*", "models/*", "tools/*"]

The lib/, models/, and tools/ directories are separate ownership boundaries: shared libraries provide reusable contracts and helpers, model packages own model-specific processing, inference, conversion, training, and configuration, and tooling packages provide repository-development helpers.

Dependency environments

A member-scoped environment installs one selected workspace package with uv sync --package <name> and is the environment used by the member test matrix for package-local development and verification. For pull requests and merge queue entries, the matrix selects test-bearing members from changed paths and transitive declared dependencies; any non-Markdown change outside a member directory selects all test-bearing members, while Markdown-only changes skip member tests. Non-Markdown main pushes, manual dispatches, and the daily scheduled run select the full member matrix. Selection follows declared dependencies, optional-dependencies, and dependency groups, always adds devharness for non-Markdown changes, and does not infer undeclared imports or shared test data.

A root-tooling environment installs the root package and its tooling group with uv sync --package design-generators --group dev, and evaluation-only commands add the root evaluation extra when they run scripts/verify_evaluate_layout_metrics.py. The root project installs no runtime dependencies by default; it owns the workspace-wide transformers and diffusers version floors and the dev and docs tooling groups, while workspace members own their runtime dependencies.

Surface Owner Consumer or contract Why root owns it
Workspace version policy Root [tool.uv].constraint-dependencies transformers>=5.0.0 and diffusers>=0.36.0; members declare what they install and satisfy these ranges The root coordinates workspace-wide transformers and diffusers versions without installing runtime packages itself.
evaluation extra Root verifier scripts/verify_evaluate_layout_metrics.py and its evaluate.load metrics The verifier is root-owned, and its metrics require the evaluation runtime including explicit opencv-python.
dev group Root tooling Root checks, tests, and pre-commit commands Repository-wide development tooling is coordinated at the root.
docs group Root tooling make setup (uv sync --all-packages --group docs) Documentation generation is a repository-wide root workflow.

A full-workspace environment installs all workspace members with uv sync --all-packages and serves as an explicit compatibility check for cross-member development and CI. Add --group docs when preparing the full checkout for documentation work, as in make setup.

Unmigrated repository checker entrypoints stay under scripts/check_*.py and own policy, diagnostics, baseline paths, CLI behavior, and git or network access; migrated checker policies live under devharness.checks and use the devharness check <name> command surface. The migrated checks include model-readmes and jaxtyping-annotations. tools/devharness owns exact reusable repository-development mechanics with at least two concrete consumers and is stdlib-only (tools/devharness/pyproject.toml declares dependencies = []), while one-consumer mechanics stay in the checker that uses them. Runtime libraries and model packages do not depend on devharness, and devharness does not depend on runtime or model packages.

Training ownership

Training infrastructure follows the current package boundaries below. Model packages retain dataset, numerical, scheduling, configuration, and model-specific parity behavior.

Concern Current owner Notes
LightningCLI/bootstrap traingen Model-agnostic repository training entrypoint used by package-scoped training environments.
Generic Lightning logging/reduction helpers traingen.lightning.steps Model-agnostic logging, loss reduction, and training-trace helpers used by package-scoped training environments.
Keras 2 Adam update semantics traingen.optim Shared by CanvasVAE and PixelVAE when comparing optimizer updates with their TensorFlow references.
RNG capture/restore and deterministic runtime controls traingen-parity Parity-only primitives are used by LayoutDM, LayoutFlow, and LayoutDiffusion adapters; model-specific seed wrappers remain local.
Trace construction/comparison/report helpers traingen-parity Generic parity primitives; model packages provide trace schemas and adapter functions.
TensorFlow 2.15 Keras compatibility for reference scripts traingen-parity traingen_parity.tensorflow_compat is used by Flex-DM reference scripts; it imports neither TensorFlow nor PyTorch at module import.
Model-specific trace point names Model package Each package owns its trace tuple; trace names are not shared merely for reuse.
Dataset/data transforms Model package Dataset formats, preprocessing, ordering, and loader behavior remain package-specific.
Loss definitions Model package Loss formulas and reduction semantics remain package-specific.
Scheduler/sampler policy Model package Optimizer cadence, timestep sampling, EMA, and scheduler behavior remain package-specific.
Training YAML/config values Model package Dataset and model configuration values live with each package's training entrypoints.
Model-specific callbacks Model package Callbacks remain local unless at least two concrete consumers have identical behavior.
Checkpoint conversion Model package Conversion code follows each model's checkpoint and topology contract.

The generated API exposes Lightning helpers under traingen.lightning.steps and the shared Keras-compatible optimizer under traingen.optim.

Runtime ownership

Shared runtime extraction requires identical semantics in at least two concrete consumers and a clearer shared owner. Model packages keep defaults, aliases, supported sets, error types and messages, device resolution, and token-space geometry when those semantics are package-specific.

Concern Current shared owner Package-local responsibility
Canonical condition vocabulary laygen.common.conditions.ConditionType None
Canonical condition aliases laygen.common.conditions.normalize_condition_type None
Vendor and model condition aliases laygen.common.conditions.normalize_condition_type provides the canonical layer Model package pipeline or processor normalizers provide vendor aliases and alias reinterpretation.
Default condition for None No single shared default Model package pipeline or processor normalizers select the model default.
Supported-condition validation No single shared supported set Model package pipeline or processor validates its supported conditions.
Required condition inputs No single shared set of required inputs Model package pipeline or processor validates inputs required by each condition.
Condition error normalization laygen.common.conditions.normalize_condition_type for unknown canonical strings Model package normalizers preserve capability errors, required-input errors, and model-specific error types and messages.
Generator-over-seed precedence laygen.pipelines.LayoutGenerationPipeline.prepare_generator for Transformers-side pipelines; laygen.common.randomness.resolve_torch_generator for Diffusers pipelines and model-internal samplers Transformers-side pipelines retain their pipeline contract; non-Transformers consumers use the shared resolver.
Side-effect-free generator construction laygen.common.randomness.resolve_torch_generator Consumers retain device resolution and no-device fallbacks; the resolver does not mutate global RNG state.
Generator-consuming random draws laygen.common.randomness.randn, rand, randint, randperm, multinomial, bernoulli, normal, and poisson Consumers pass the requested output device and dtype; wrappers draw on the generator device and move results to the requested target.
Transformers global seed behavior laygen.pipelines.LayoutGenerationPipeline.prepare_generator, which calls transformers.set_seed() only when a seed is given without an explicit generator Transformers-side pipelines inherit this; Diffusers pipelines and samplers never seed global state.
Generator device resolution laygen.pipelines.LayoutGenerationPipeline.prepare_generator and laygen.common.randomness.resolve_torch_generator construct seeded generators on CPU; explicit generators retain their device Consumers resolve output devices at their existing boundaries and pass them to the shared draw wrappers.
Both-absent generator behavior laygen.pipelines.LayoutGenerationPipeline.prepare_generator for Transformers-side pipelines; laygen.common.randomness.resolve_torch_generator for Diffusers pipelines and model-internal samplers Consumers leave the generator absent when no seed is supplied; Transformers-side pipelines retain their existing global seed behavior.
Output mode vocabulary No single public output-mode enum Pipelines, processors, and agents expose local dataclass and dict modes, with local enum spelling and error behavior.
Dataclass or dictionary formatting laygen.modeling_outputs.LayoutGenerationOutput and laygen.pipelines.pipeline_output.LayoutGenerationOutput define the common dataclasses Model packages and agents format common outputs or cast dictionaries according to their public return types.
Public bounding-box normalization laygen.common.bbox Model packages apply model-specific quantization, discretization, coordinate conventions, and checkpoint transforms.
Dataset and label vocabulary laygen.common.labels and posgen.common.labels Model packages preserve task-specific, poster-specific, open-vocabulary, and token-offset mappings.
Serialization and token sequences No single shared serializer Model package tokenizers and processors own vendor token order, special tokens, offsets, and sequence schemas.
Public argument validation laygen.common.bbox Model packages validate model capability, numerical, shape, batch, and payload constraints.

Agent and model-card metadata ownership

The provider-backed agents are LayoutGPT, LayoutPrompter, and PosterO. A package README and a generated checkpoint model card are separate artifacts with separate lifecycles.

Concern Shared owner
Provider model resolution laygen.agents.BaseLayoutAgent.resolve_model hook, called from build_pydantic_agent; each agent package passes its own model_env_var, while LayoutPrompter and PosterO override the hook with their fallback chains.
Pydantic AI Agent construction laygen.agents.BaseLayoutAgent; each agent package keeps its raw response schema and provider instructions.
Raw synchronous provider execution laygen.agents.BaseLayoutAgent.run_raw_sync; each agent package keeps prompt construction and model-settings values.
Public agent request validation laygen.agents.BaseLayoutAgent.validate_generation_request, used by LayoutGPT; PosterO validates in its own _validate_request, while LayoutPrompter accepts batch and canvas arguments without validating them.
Output dict serialization laygen.agents.BaseLayoutAgent.output_to_dict; agent packages keep their OutputType enums, package TypedDict types, and casts.
Repair/retry framework laygen.agents.BaseLayoutAgent repair/retry hooks and loop; no agent calls run_with_repair_policy, and PosterO runs its own parser-retry loop.
Generic model-card rendering laygen.common.model_card.build_layout_model_card; model packages keep model facts, metadata values, wording, parity rows, citations, and upstream references.

Config persistence and loading, exemplars, parsers, Hub dataset and model identity, checkpoint README writing, and package READMEs remain package-owned with no shared owner.

Shared package names and imports

laygen is the shared layout-generation library; its installable package name and its import name are both laygen. posgen is the shared poster and content-aware library; its installable package name and its import name are both posgen.

Use laygen.common for helpers that are reusable across layout-generation packages, and use posgen.common for helpers that are reusable across poster or content-aware packages.

Ownership boundaries

Concern Shared owner Model-package responsibility
Layout boxes, labels, discrete helpers, visualization, serialization, testing, and other proven layout-wide helpers laygen.common Apply model-specific coordinate conventions, token order, checkpoint details, and parity behavior.
Poster content, poster labels, poster testing, and poster visualization that are shared by concrete consumers posgen.common Keep model-specific image processing, saliency behavior, retrieval, prompt parsing, tokenization, scheduling, and configuration local.
Public output types and pipeline contracts laygen public modules Return the repository's common schema while preserving model-specific optional data in the documented fields.

Move a helper into a shared package when at least two packages need the same behavior or when a shared public contract must exist before a second consumer lands. Do not create a speculative abstraction without concrete shared behavior.

Dependency direction

All model packages may import laygen directly. Poster or content-aware model packages may additionally import posgen; posgen may depend on laygen when shared layout primitives are needed. posgen declares laygen in its test dependency group because its tests import laygen.common. Shared libraries never import model packages.

laygen must not import posgen or model packages, and posgen must not import model packages.

Root tooling and scripts/check_*.py may import devharness. The devharness tooling package must not import laygen, posgen, or model packages, and runtime libraries and model packages must not import devharness.

The Transformers-side shared layout output in laygen.modeling_outputs is based on transformers.utils.ModelOutput, not diffusers.utils.BaseOutput. The Diffusers-side output in laygen.pipelines.pipeline_output may use the Diffusers base type behind the optional diffusion extra. The laygen core dependencies must not gain a hard diffusers dependency.

graph LR
    laygen["laygen"] --> model_packages["model packages"]
    laygen -. "test dependency" .-> posgen["posgen"]
    posgen --> poster_models["poster or content-aware model packages"]

The arrows point from a library to the packages that import it.

posgen is a workspace member, and current poster/content-aware consumers use it alongside laygen.

Working with the architecture

Import shared helpers using their public module paths, such as from laygen.common.bbox import normalize_boxes or from posgen.common import PositionContent.

Keep model-specific code in the model package even when it resembles a shared helper, and add a shared helper only when its behavior and ownership are clear to its consumers.

This page defines where packages live, which package owns each import name, dependency direction, and output dependency boundaries; the current public output fields and pipeline contracts remain documented in Conventions, and API details remain in the generated reference.