Use it from your code
Everything the clika-modelverse executable did in parts 1 through 3 is a library call. This part writes the smallest program that does what prompt does: resolve a snapshot, load the runnable model, and generate.
The project
For C++ and C, the install directory you already have is also the SDK (the headers, the library, and the ClikaRT runtime beside them), and a two-file CMake project is the whole setup. The C++17 toolchain from the ClikaRT quick install prerequisites is the one requirement.
cmake_minimum_required(VERSION 3.20)
project(hello_modelverse LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Modelverse CONFIG REQUIRED)
add_executable(hello main.cpp)
target_link_libraries(hello PRIVATE Modelverse::modelverse)
# Copy the Modelverse library and the ClikaRT runtime next to the binary,
# so the program runs from the build directory as-is.
modelverse_stage_runtime(TARGET hello)
find_package(Modelverse CONFIG) wires everything: it defines the one link target (Modelverse::modelverse), and finds the ClikaRT runtime installed in the same root (a ClikaRT::ClikaRT you already provide is respected instead).
Kotlin, Go and Rust arrive through their package managers, with the runtime artifact as a declared dependency that loads first; Python needs no package at all, because its path is the served model from part 3 and any OpenAI client:
cargo add clika-modelverse # Rust (pulls clika-rt)
go get github.com/Clika/clika_runtime/bindings/go # Go (one module: clikart + modelverse)
# Kotlin/Gradle: implementation("io.clika:clika-modelverse:<version>")
The program
- C++
- C
- Python
- Kotlin
- Go
- Rust
#include <cstdio>
#include "clika_modelverse/hub/hub.h"
#include "clika_modelverse/registry/model_registry.h"
#include "clika_modelverse/runtime/serving_pipeline.h"
#include "clika_modelverse/runtime/text_nodes.h"
namespace mv = clika_modelverse;
namespace mvr = clika_modelverse::runtime;
int main() {
// 1. Model files, exactly as `fetch` gets them. An already-fetched or
// local directory passes through untouched.
mv::hub::SnapshotOptions snap;
snap.cache_dir = "models"; // beside the program; empty = the shared Hugging Face cache
const mv::hub::SnapshotResult snapped = mv::hub::snapshot(
"meta-llama/Llama-3.2-1B-Instruct", snap);
// 2. The registry matches the snapshot to its family and returns the
// runnable model, on the device you name (CPU by default).
mv::LoadOptions load;
load.max_seq = 4096; // the CLI's default context cap; 0 keeps the
// checkpoint's full window and sizes the KV cache from it
mv::GenerativeModel model =
mv::ModelRegistry::builtin().load_generative(snapped.local_dir, load);
// 3. A serving pipeline around it: tokenizer -> decoder -> detokenizer,
// the same assembly `prompt` and `serve` run on.
mv::generation::GenerationConfig gen = model.defaults();
gen.max_new_tokens = 64;
gen.stop = {"."}; // raw completion: stop at the first sentence end
mvr::PipelineOptions opts;
mvr::GenerativePipeline pipe = mvr::build_generative_pipeline(model, gen, opts);
// 4. One request through it.
ClikaRT::runtime::Request req;
req.inputs.set("prompt", mvr::string_to_byte_tensor(
"Once upon a time, in a port town by a cold sea,"));
const auto sid = pipe.pipeline->enqueue(std::move(req));
const ClikaRT::runtime::Response resp = pipe.pipeline->await(sid);
std::printf("%s\n", mvr::byte_tensor_to_string(resp.outputs.get("text")).c_str());
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
#include "clika_modelverse/clika_modelverse_core.h"
/* The api-table bootstrap shared by every C sample: dlopen the library,
* handshake at ABI version 1, exit loudly when either step fails. */
static const clika_modelverse_api* api;
static void bootstrap(void) {
void* so = dlopen("libclika_modelverse.so", RTLD_NOW | RTLD_GLOBAL);
if (so == NULL) { fprintf(stderr, "dlopen: %s\n", dlerror()); exit(1); }
const clika_modelverse_api* (*get_api)(uint32_t) =
(const clika_modelverse_api* (*)(uint32_t))dlsym(so, "clika_modelverse_get_api");
api = get_api != NULL ? get_api(1u) : NULL;
if (api == NULL) { fprintf(stderr, "clika_modelverse_get_api(1) failed\n"); exit(1); }
}
static void check(int rc) {
if (rc != 0) { fprintf(stderr, "%s\n", api->last_error()); exit(1); }
}
int main(void) {
bootstrap();
/* 1. Model files, exactly as `fetch` gets them. */
clika_mv_snapshot* snap = NULL;
check(api->hub_snapshot("meta-llama/Llama-3.2-1B-Instruct",
/*cache_dir=*/"models", &snap));
/* 2. The runnable model, on the default device. */
clika_mv_model* model = NULL;
check(api->load_generative(api->snapshot_dir(snap), /*options=*/NULL, &model));
/* 3. The serving pipeline around it. */
clika_mv_pipeline* pipe = NULL;
check(api->pipeline_create(model, /*max_new_tokens=*/64, &pipe));
/* 4. One request through it. */
char* text = NULL;
check(api->pipeline_generate(pipe, "The capital of France is", &text));
printf("%s\n", text);
api->string_release(text);
api->pipeline_release(pipe);
api->model_release(model);
api->snapshot_release(snap);
return 0;
}
# From python, use the served model: one URL, any OpenAI client.
# Serve it once (from a terminal):
# clika-modelverse meta-llama/Llama-3.2-1B-Instruct serve
from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8000/v1", api_key="unused")
reply = client.chat.completions.create(
model="llama",
messages=[{"role": "user", "content": "The capital of France is"}],
max_tokens=32,
)
print(reply.choices[0].message.content)
import io.clika.modelverse.Hub
import io.clika.modelverse.ModelRegistry
import io.clika.modelverse.Pipelines
fun main() {
// 1. Model files, exactly as `fetch` gets them.
val snapped = Hub.snapshot(
"meta-llama/Llama-3.2-1B-Instruct",
cacheDir = "models", // beside the program; null = the shared Hugging Face cache
)
// 2. The registry matches the snapshot to its family and returns the
// runnable model, on the device you name (CPU by default).
val model = ModelRegistry.builtin().loadGenerative(snapped.localDir)
// 3. A serving pipeline around it, the same assembly `prompt`,
// and `serve` run on.
val pipe = Pipelines.generative(model, maxNewTokens = 64)
// 4. One request through it.
println(pipe.generate("The capital of France is"))
}
package main
import (
"fmt"
"log"
"github.com/Clika/clika_runtime/bindings/go/clikart"
"github.com/Clika/clika_runtime/bindings/go/modelverse"
)
// must keeps the happy path readable; real programs branch on the error.
func must[T any](v T, err error) T {
if err != nil {
log.Fatal(err)
}
return v
}
func main() {
// The runtime loads first; the modelverse package rides its handle.
api := must(clikart.Load("libClikaRT.so"))
// 1. Model files, exactly as `fetch` gets them. An already-fetched or
// local directory passes through untouched.
snapped := must(modelverse.HubSnapshot(api,
"meta-llama/Llama-3.2-1B-Instruct",
modelverse.SnapshotOptions{CacheDir: "models"}))
// 2. The registry matches the snapshot to its family and returns the
// runnable model, on the device you name (CPU by default).
model := must(modelverse.LoadGenerative(api, snapped.LocalDir, nil))
// 3. A serving pipeline around it, the same assembly `prompt`,
// and `serve` run on.
pipe := must(modelverse.NewGenerativePipeline(model,
modelverse.PipelineOptions{MaxNewTokens: 64}))
// 4. One request through it.
fmt.Println(must(pipe.Generate("The capital of France is")))
}
use clika_modelverse::{hub, ModelRegistry};
fn main() -> Result<(), clika_modelverse::Error> {
// 1. Model files, exactly as `fetch` gets them. An already-fetched or
// local directory passes through untouched.
let snapped = hub::snapshot(
"meta-llama/Llama-3.2-1B-Instruct",
hub::SnapshotOptions {
cache_dir: Some("models".into()), // None = the shared Hugging Face cache
..Default::default()
},
)?;
// 2. The registry matches the snapshot to its family and returns the
// runnable model, on the device you name (CPU by default).
let model = ModelRegistry::builtin().load_generative(&snapped.local_dir, Default::default())?;
// 3. A serving pipeline around it, the same assembly `prompt`,
// and `serve` run on.
let mut pipe = clika_modelverse::build_generative_pipeline(&model, 64)?;
// 4. One request through it.
println!("{}", pipe.generate("The capital of France is")?);
Ok(())
}
Build and run the C++ project:
cmake -S . -B build -DModelverse_DIR="$MODELVERSE_INSTALL_DIR/cmake"
cmake --build build
./build/hello
there lived a young woman named Akira.
The raw pipeline CONTINUES text; there is no chat template in the loop, which is the visible difference from prompt in part 2 (templated, answers a question). That is why this program feeds it a story opener and stops at the first sentence end: give a raw completion a question and it rambles on in the question's shape. For question-answering from the library, wrap the message the way the executable does, or serve the model and use the chat route as the Python tab shows.
What the four steps are
The program is the executable's anatomy laid bare, and each step is independently useful:
- The snapshot call resolves any source (a Hugging Face Hub repo id, URL, or local directory) to a directory of model files. Point it at a directory you shipped with your application and no network code is ever built in.
- The registry load is the catalog from part 1 as a function: identity match, factory, weights onto the device. Where the weights load is
LoadOptions::where, aStreamor aDevice: a Device (or nothing; thedevicefield then decides) loads through a fresh stream of the model's own, a Stream loads through yours, behind whatever it already carries. The load synchronizes the weights, so their stream stops mattering once it returns; each inference call then runs on the stream its input tensors arrive on (or the calling thread's stream when they carry none), which is what lets two models fed from two streams overlap. The CLI's--devicefills the same option. - The pipeline build assembles the serving pipeline. It is a ClikaRT serving-runtime pipeline underneath, so sessions, continuous batching and the async model documented there apply unchanged.
- The request is the generation loop.
servefrom part 3 is this loop behind HTTP; the library'sOpenAiServerlets you mount the same engines, or your own, in-process.
Failures follow the runtime's error model per language: C++ returns values directly and raises ClikaRT::Error, Rust returns Result, Go returns (value, error) with errors.As for the typed read, C returns status codes with last_error(), and Python's served route reports errors as HTTP statuses the OpenAI client raises.
You have taken a model from the catalog to your own binary. What to read next.