Skip to main content

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.json with model_type and architectures; these two values are the identity keys a registration matches on.
  • The processor files the model needs (tokenizer.json and 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:

my_model_family.cpp
#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"};

// Static-init self-registration: constructing the registrar is the whole hookup.
const ModelFamilyRegistrar kRegistrar{
ModelRegistration{
.metadata = ModelMetaData{
.family = "my-model",
.vendor = "my-org",
.description = "the in-house my_model language models",
.input_combos = kTextOnlyCombos,
.output_combos = kTextOnlyCombos,
},
.hf_model_types = kModelTypes,
.hf_architectures = kArchitectures,
.factory = make_model<MyModel>(),
},
};

} // 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 clika-modelverse binary knows only the built-in families). An incomplete declaration (no family name, 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_generative materializes 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 the clika_modelverse/modules/ building blocks (attention, decoder stack, projections, rope cache) or your own nn::Module, and return a GenerativeModel wrapping 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.
  • .cli declares the family's commands. The battery builders compose the standard text surface in one line, and with it your family's prompt, serve and bench behave exactly as the tutorial documents them:
.cli = CliSurface{batteries::cli::llm_cli, batteries::cli::llm_cli_verbs()},
.factory = make_model<MyModel>(),
.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.