Skip to content

feat: add torchembed optional backend for categorical embeddings - #663

Open
py-ai-dev wants to merge 1 commit into
pytorch-tabular:mainfrom
py-ai-dev:feat/torchembed-embedding-backend
Open

feat: add torchembed optional backend for categorical embeddings#663
py-ai-dev wants to merge 1 commit into
pytorch-tabular:mainfrom
py-ai-dev:feat/torchembed-embedding-backend

Conversation

@py-ai-dev

Copy link
Copy Markdown

Summary

  • Adds an embedding_backend field to ModelConfig (choices: "native" | "torchembed", default "native") so users can opt in to torchembed (v0.3.1+) as the categorical embedding engine with a single config change.
  • When "torchembed" is selected, Embedding1dLayer delegates to torchembed.categorical.MultiCategoricalEmbedding, which fuses all per-column embeddings into a single module with auto-sized dimensions.
  • The output shape is identical to the native path, so no other code changes are needed in downstream models or heads.
  • torchembed is an optional dependency (pip install pytorch-tabular[torchembed]); the native path is completely unchanged.

Motivation

pytorch-tabular currently builds one nn.Embedding per categorical column and requires the user to either accept the min(50, (cardinality+1)//2) heuristic or supply explicit embedding_dims. torchembed exposes the same heuristic through a cleaner, single-module API (MultiCategoricalEmbedding) that:

  • computes and validates per-column dims automatically
  • exposes a single output_dim attribute instead of requiring callers to sum over a list of tuples
  • makes it straightforward to swap in future improvements (e.g. learned shared embeddings, quantised embeddings) without touching model code

Usage

from pytorch_tabular.models import CategoryEmbeddingModelConfig

config = CategoryEmbeddingModelConfig(
    task="regression",
    layers="128-64",
    embedding_backend="torchembed",   # <-- only change needed
)
# Low-level — works identically with either backend
from pytorch_tabular.models.common.layers import Embedding1dLayer

layer = Embedding1dLayer(
    continuous_dim=4,
    categorical_embedding_dims=[(50, 25), (7, 4), (120, 50)],
    embedding_backend="torchembed",
)
x = {"categorical": cat_ids, "continuous": cont_feats}
out = layer(x)          # (batch, 4 + layer.output_dim)
print(layer.output_dim) # e.g. 79

Files changed

File Change
src/pytorch_tabular/models/common/layers/embeddings.py Embedding1dLayer: new embedding_backend param, output_dim property, torchembed forward path
src/pytorch_tabular/config/config.py ModelConfig: new embedding_backend field + docstring
src/pytorch_tabular/models/category_embedding/category_embedding_model.py forwards embedding_backend from config
pyproject.toml new [project.optional-dependencies] torchembed group
tests/test_torchembed_backend.py 7 unit tests (native shape, torchembed shape, differentiability, helpful import error)

Notes

  • Only CategoryEmbeddingModel wires the option end-to-end in this PR. Other models that use Embedding1dLayer (DANet, GANDALF, GATE, NODE) can be updated the same way in follow-up PRs — the layer-level API is already there.
  • If embedding_backend="torchembed" is set and torchembed is not installed, a clear ImportError with an install command is raised at Embedding1dLayer.__init__ time, not silently at forward time.

Test plan

  • pytest tests/test_torchembed_backend.py -v — 7/7 pass (torchembed installed)
  • torchembed-backend tests automatically skip (pytest.importorskip) when torchembed is absent, so existing CI remains green with no new required dependency

🤖 Generated with Claude Code

Adds an `embedding_backend` config option to `ModelConfig` (default
`"native"`) that accepts `"torchembed"` as a value.  When selected,
`Embedding1dLayer` delegates to
`torchembed.categorical.MultiCategoricalEmbedding` instead of building
one `nn.Embedding` per column.  Benefits over the native path:

- Auto-sized embedding dimensions (standard `min(50,(n+1)//2)` rule)
  computed and managed inside a single fused module
- Cleaner API: one module owns all categorical columns
- Drop-in: output shape is identical so no downstream changes are needed

Changes:
- `Embedding1dLayer` gains `embedding_backend` param and an `output_dim`
  property (useful for both backends)
- `ModelConfig` gains the `embedding_backend` field with docstring
- `CategoryEmbeddingModel._build_embedding_layer` forwards the option
- `pyproject.toml` gains a `[torchembed]` optional-dependency group
- `tests/test_torchembed_backend.py` covers native path, torchembed
  output shapes, differentiability, and helpful ImportError messaging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional categorical-embedding backend (torchembed) to pytorch-tabular so users can switch embedding implementations via config while keeping the same downstream tensor shapes.

Changes:

  • Introduces embedding_backend (default "native") on ModelConfig, and wires it through CategoryEmbeddingModel.
  • Extends Embedding1dLayer to support a torchembed-powered path and adds an output_dim property.
  • Adds an optional dependency group for torchembed and a new unit test module covering both backends.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/test_torchembed_backend.py Adds backend-focused unit tests (but currently skips the whole module when torchembed is absent).
src/pytorch_tabular/models/common/layers/embeddings.py Implements embedding_backend switch and output_dim; needs a guard for cat_embedding_layers access under torchembed.
src/pytorch_tabular/models/category_embedding/category_embedding_model.py Forwards embedding_backend from hparams into Embedding1dLayer.
src/pytorch_tabular/config/config.py Adds embedding_backend field and help text to ModelConfig.
pyproject.toml Adds torchembed optional-dependency group.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +78 to +82
torchembed = pytest.importorskip(
"torchembed",
reason="torchembed not installed; skipping torchembed-backend tests. "
"Install with: pip install torchembed",
)
Comment on lines +102 to +104
"""output_dim must match MultiCategoricalEmbedding.output_dim."""
from torchembed.categorical import MultiCategoricalEmbedding

Comment on lines +150 to +153
elif embedding_backend == "native":
# Native per-column embedding layers (original behaviour)
self.cat_embedding_layers = nn.ModuleList([nn.Embedding(x, y) for x, y in categorical_embedding_dims])
self._cat_output_dim = sum(dim for _, dim in categorical_embedding_dims)
Comment on lines +168 to +171
@property
def output_dim(self) -> int:
"""Total output dimension of the categorical embeddings produced by this layer."""
return self._cat_output_dim
Comment on lines +143 to +146
raise ImportError(
"The 'torchembed' package is required when embedding_backend='torchembed'. "
"Install it with: pip install torchembed"
) from exc
Comment on lines +86 to +87
"""torchembed backend must produce the same 2-D output shape as the native backend."""
layer = Embedding1dLayer(
Comment on lines +115 to +116
"""Gradients must flow through the torchembed embedding layer."""
layer = Embedding1dLayer(
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants