Register your own model family
The catalog covers the common architectures; your in-house model is not in it. Modelverse's answer is self-registration: a family is a translation unit that pushes a ModelRegistration into the shared registry at static-initialization time, and every built-in family registers exactly this way. There is no central list to edit. You compile one registration TU into your program against the public headers, and list, info and the loaders treat your family like any other. This page is the contract at full depth; for the guided walk from a hand-written checkpoint to a running family of your own, start with the Adding your own model tutorial series; this page's worked example is a text-generation family, the detail-heavy case the tutorial deliberately avoids.
What a snapshot must contain
A model is a directory of files, and identity resolution reads only its configuration:
config.jsonwithmodel_typeandarchitectures; these two values are the identity keys a registration matches on.- The processor files the model needs (
tokenizer.jsonand friends for text; preprocessor configs for image and audio). - The weights (
*.safetensors, ONNX, or GGUF); untouched at identity time.
A repo that ships no config.json can still be matched: a registration may carry a probe_snapshot function that recognizes the family from the file set alone and answers the model_type to route as.
The registration, identity first
One complete TU. With only this much, list shows the family (identity-only), info resolves your checkpoints to it, and every runnable command refuses precisely, naming what the family provides:
#include "clika_modelverse/models/registration.h"
namespace {
using namespace clika_modelverse;
// The identity template: what a resolved checkpoint of this family IS.
// The registry fills architecture, model_type and variant from config.json.
class MyModel final : public Model {
public:
std::string_view family() const noexcept override { return "my-model"; }
};
// The identity keys, matched against config.json.
constexpr std::string_view kModelTypes[] = {"my_model"};
constexpr std::string_view kArchitectures[] = {"MyModelForCausalLM"};
// The Hugging Face pipeline tags the family's checkpoints carry, by the
// constants of models/base/pipeline_tags.h (the registrar refuses a literal
// outside that vocabulary).
constexpr std::string_view kPipelineTags[] = {pipeline_tags::kTextGeneration};
// Static-init self-registration: constructing the registrar is the whole hookup.
const ModelFamilyRegistrar kRegistrar{
[] {
ModelRegistration registration{};
registration.metadata.family = "my-model";
registration.metadata.vendor = "my-org";
registration.metadata.pipeline_tags = kPipelineTags;
registration.metadata.description = "the in-house my_model language models";
registration.metadata.input_combos = kTextOnlyCombos;
registration.metadata.output_combos = kTextOnlyCombos;
registration.hf_model_types = kModelTypes;
registration.hf_architectures = kArchitectures;
registration.factory = make_model<MyModel>();
return registration;
}(),
};
} // namespace
Compile that TU into any program that links Modelverse::modelverse (the command-dispatch functions in the library serve programs that embed them; the shipped clikart-cli binary knows only the built-in families). An incomplete declaration (no family name, no vendor, no pipeline tag or one outside the Hugging Face vocabulary, no modalities, no identity factory) is refused at process start, not papered over downstream.
my-model identity-only
* Input Modalities: text
* Output Modalities: text
* Commands: none (identity-only)
* Vendor: my-org
Making it runnable
Two more fields turn identity into execution:
build_generativematerializes the runnable model from a resolved snapshot: read the config, adapt the checkpoint's tensor names where they differ from what your forward expects (ClikaRT::io::TensorsAdapter, rename-at-lookup, no payload copies), assemble the forward from theclika_modelverse/modules/building blocks (attention, decoder stack, projections, rope cache) or your ownnn::Module, and return aGenerativeModelwrapping it with the snapshot's tokenizer and generation defaults. The GGUF twin,build_generative_gguf, receives the already-loaded file so identity read and weight bind share one mapping.clideclares the family's commands. The battery builders compose the standard text surface in one line, and with it your family'sprompt,serveandbenchbehave exactly as the tutorial documents them:
registration.cli = CliSurface{batteries::cli::llm_cli, batteries::cli::llm_cli_verbs()};
registration.factory = make_model<MyModel>();
registration.build_generative = build_my_model;
A family wanting a different surface composes its own FamilyApp from the same public builders (batteries/cli/prompt.h, serve.h, bench.h), one verb per builder; the advertised verb list and the App are pinned to agree by the registration contract.
Custom behavior around an EXISTING family needs none of this: Add your own node to a model pipeline composes your logic with any catalog model. The complete field-by-field contract, including the speech, embedding and reranking factories, is clika_modelverse/models/registration.h in the installed headers.
The nearest family to start from
A checkpoint no family claims is refused by name: clikart-cli info <source> prints the model_type and architectures[0] it read and that no template serves them, and clikart-cli list prints every family with the keys it claims, so the family whose definition the checkpoint's config.json shares is the one to read first. A composite whose config.json carries a nested text_config naming a registered family composes that family's text encoder through the text-backbone seam (build_text_backbone on the registration), with no forward of its own for the tower. A decoder driven by the embeddings of an encoder you wrote has no such seam in this release: its forward is written from the modules, as the section above does for a text-generation family.