Base for every Lucid model that supports the pretrained-checkpoint flow.
Subclasses inherit from_pretrained / save_pretrained
plus parameter-counting and embedding-access helpers. The contract:
- Set
config_class = MyConfigas aClassVar— required; enforced at__init__time. - Define
__init__(self, config: MyConfig) -> Nonetaking a single config argument. All architectural variation (depth, width, activation choices, …) belongs inside the config — no extra constructor parameters. - Implement
forward(...) -> ModelOutputreturning one of the dataclasses inlucid.models._output.
Attributes
config_classClassVar[type[ModelConfig] or None]None signals "not set" — the base
__init__ raises in that case.base_model_prefixClassVar[str]"bert" on
BERTForMaskedLM). Used by future state-dict remapping logic
when loading head-less checkpoints into head-bearing models.configModelConfig__init__.Notes
The class is a thin lift on lucid.nn.Module — all parameter
management still flows through the standard Module machinery.
Two persistence formats are supported when saving / loading: the
pickle-based weights.lucid (default) and the cross-framework
model.safetensors.
Examples
>>> import lucid
>>> import lucid.nn as nn
>>> from dataclasses import dataclass
>>> from typing import ClassVar
>>> from lucid.models import ModelConfig, PretrainedModel
>>> @dataclass(frozen=True)
... class MyConfig(ModelConfig):
... model_type: ClassVar[str] = "myfamily"
... hidden_size: int = 768
... num_classes: int = 10
>>> class MyModel(PretrainedModel):
... config_class: ClassVar[type[MyConfig]] = MyConfig
... def __init__(self, config):
... super().__init__(config)
... self.linear = nn.Linear(config.hidden_size, config.num_classes)
... def forward(self, x):
... return self.linear(x)
>>> model = MyModel(MyConfig(hidden_size=128, num_classes=10))
>>> model.num_parameters()
1290Used by 58
- lucid.models
- lucid.models._auto
- lucid.models._registry
- lucid.models._tasks
- lucid.models.generative.ddpm._model
- lucid.models.generative.diamond._model
- lucid.models.generative.dit._model
- lucid.models.generative.dreamer._model
- lucid.models.generative.dreamer_v2._model
- lucid.models.generative.dreamer_v3._model
- lucid.models.generative.flow_matching._model
- lucid.models.generative.genie._model
… 46 more
Constructors
1Initialise the module and validate the supplied config.
Parameters
configModelConfigconfig_class.Raises
TypeErrorconfig_class has not been set on the concrete subclass,
or if config is not an instance of config_class.Class methods
1Load a model from a registered name or a local directory.
Two modes are supported:
- Registered name (
"resnet_50"/"resnet-50") — looked up in the global registry; the factory is invoked withpretrained=True. The factory result is validated to be an instance ofcls(so subclass calls likeResNet.from_pretrained("vit_base_16")raise rather than silently returning the wrong family). - Local directory containing
config.jsonplus eithermodel.safetensors(preferred) orweights.lucid. The config is restored viacls.config_class.loadand the weights are loaded withlucid.load.
Parameters
name_or_pathstrstrict(bool, optional, keyword - only)= Trueload_state_dict.Returns
SelfA fully constructed model instance.
Raises
ValueErrorFileNotFoundErrorTypeErrorcls,
or config_class is unset, or the weights file lacks a
state-dict.Notes
For task-aware dispatch that resolves to a different concrete
subclass per task, use the AutoModelFor* family instead.
Examples
>>> # Registered name
>>> model = ResNetForImageClassification.from_pretrained("resnet_50")
>>>
>>> # Local directory
>>> model.save_pretrained("/tmp/my_resnet50")
>>> reloaded = ResNetForImageClassification.from_pretrained("/tmp/my_resnet50")Instance methods
4Return the input-embedding submodule, or None if not applicable.
Returns
nn.Module or NoneThe embedding layer for text / token-id models (BERT, GPT,
…); None for vision and other non-embedding models.
Notes
Trunks with an embedding table override this. A task wrapper does
not need to: this implementation hands the call to the trunk stored
under base_model_prefix, so a wrapper reports the same table
as the trunk inside it. Used by tools that need to resize / share /
introspect token embeddings without coupling to family-specific
attribute names.
Examples
>>> from lucid.models import create_model
>>> lm = create_model("gpt_lm", vocab_size=100, hidden_size=16,
... num_hidden_layers=1, num_attention_heads=2,
... intermediate_size=32, max_position_embeddings=8)
>>> lm.get_input_embeddings() is lm.transformer.tokens_embed
TrueReturn the total number of elements across all parameters.
Parameters
only_trainable(bool, optional, keyword - only)= FalseTrue, parameters with requires_grad=False are
excluded (useful for reporting trainable model size after
freezing the backbone).Returns
intSum of prod(p.shape) over the selected parameters.
Examples
>>> model = create_model("resnet_50")
>>> model.num_parameters()
25557032
>>> for p in model.backbone.parameters():
... p.requires_grad = False
>>> model.num_parameters(only_trainable=True) < 25557032
TrueWrite config.json and weights to path.
Parameters
pathstrsafe_serialization(bool, optional, keyword - only)= FalseTrue, save weights as model.safetensors (requires
pip install safetensors). If False, save as
weights.lucid using the native pickle-based format.Notes
Output layout:
path/
config.json
model.safetensors # when safe_serialization=True
weights.lucid # when safe_serialization=False
The companion from_pretrained (or any AutoModelFor*
class) reads this directory layout and prefers SafeTensors when
both files are present.
Examples
>>> model = create_model("resnet_50")
>>> model.save_pretrained("/tmp/my_resnet50", safe_serialization=True)Replace the input-embedding submodule.
Parameters
valuenn.ModuleRaises
NotImplementedErrorbase_model_prefix.Notes
Companion to get_input_embeddings. Trunks with a table
override both; a task wrapper inherits this implementation, which
makes the swap on its trunk.
When the wrapper ties an output head to the table
(config.tie_word_embeddings), the head is re-bound to the new
table's weight, so the two stay one matrix and the head scores the
new vocabulary. A head bias sized to the vocabulary is resized with
it: existing entries are kept and new ones start at zero. An untied
head is left as it is. Either way config is frozen and keeps
its original vocab_size.
Examples
A text trunk hands its table over, so a larger one can be swapped
in — here to make room for twenty token ids the original 100-row
table could not look up.
>>> import lucid
>>> import lucid.nn as nn
>>> from lucid.models import create_model
>>> model = create_model("gpt", vocab_size=100, hidden_size=16,
... num_hidden_layers=1, num_attention_heads=2,
... intermediate_size=32,
... max_position_embeddings=8).eval()
>>> bigger = nn.Embedding(120, 16)
>>> model.set_input_embeddings(bigger)
>>> model.get_input_embeddings() is bigger
True
>>> ids = lucid.tensor([[110, 3]], dtype=lucid.int64)
>>> model(ids).last_hidden_state.shape
(1, 2, 16)
A task wrapper makes the same swap on its trunk. Its LM head is tied
to the table, so the head follows and the logits widen to the new
vocabulary, rather than keep scoring the table it replaced.
>>> lm = create_model("gpt_lm", vocab_size=100, hidden_size=16,
... num_hidden_layers=1, num_attention_heads=2,
... intermediate_size=32,
... max_position_embeddings=8).eval()
>>> lm.set_input_embeddings(nn.Embedding(120, 16))
>>> lm.lm_head.weight is lm.get_input_embeddings().weight
True
>>> lm(ids).logits.shape
(1, 2, 120)
A model with no embedding table keeps this base implementation and
refuses, rather than attach a module that nothing would read.
>>> lenet = create_model("lenet_5")
>>> lenet.get_input_embeddings() is None
True
>>> lenet.set_input_embeddings(nn.Embedding(10, 4))
Traceback (most recent call last):
...
NotImplementedError: LeNet does not support set_input_embeddings