modelc
Package LLM weights into one portable file. Serve an OpenAI-compatible API. All from a single Rust CLI.
modelc is a Rust CLI that compiles model weight files — GGUF, Safetensors, ONNX, and PyTorch — into a single self-contained
.modelcartifact and runs an OpenAI-compatible inference server (/v1/chat/completions,/v1/completions,/v1/embeddings). Think of it as a build tool plus a lightweight Ollama-style local runtime, in one binary, with native Apple Silicon (Metal) acceleration and CPU SIMD.
What is modelc?
modelc is a command-line tool for working with local language-model weight files. It does two jobs:
- Package weights into one
.modelcfile (JSON header + compressed tensor blob, optional INT4/INT8/FP16 quantization and pruning). - Serve that artifact as an HTTP inference API that speaks the OpenAI protocol, so existing clients and SDKs work without changes.
It supports GPT-2 and LLaMA transformer forward passes, MLP models, and ONNX graph execution directly from the in-memory runtime — no Python, no PyTorch, no large framework runtime required. Output binaries are pure Rust (axum + tokio).
Cross-platform
Runs on macOS, Linux, and Windows from the same Rust toolchain. On Apple Silicon (M1–M4) it uses the Metal GPU for matmul/elementwise kernels; on x86_64 and aarch64 it uses AVX / NEON SIMD GEMV with a scalar fallback.
How it compares
| Capability | modelc | Ollama | vLLM | llama.cpp |
|---|---|---|---|---|
| Implementation language | Rust | Go (wraps llama.cpp) | Python + CUDA | C++ |
| Model artifact | single .modelc file |
layered blobs | HuggingFace weights | GGUF file |
| OpenAI-compatible API | ✅ /v1/* |
✅ | ✅ | ✅ |
| Native GPU | Metal (macOS) | via llama.cpp | CUDA / ROCm | CUDA, Metal, Vulkan |
| CPU SIMD | ✅ AVX / NEON GEMV | via llama.cpp | limited | ✅ |
| GGUF / Safetensors / ONNX / PyTorch | ✅ all four | GGUF | HF / Safetensors | GGUF |
| Quantization | INT4 / INT8 / FP16 at pack | GGUF quants | AWQ / GPTQ | GGUF quants |
| Speculative decoding, prefix cache, KV quant | ✅ | via llama.cpp | ✅ | ✅ |
Standalone emitted binary (compile) |
✅ | ❌ | ❌ | ❌ |
| Best fit | packaging, portable single-file artifacts, lightweight serving | model zoo + chat app | high-throughput GPU serving | widest model support / edge |
modelc's niche is packaging and portability: one tool that compiles, optimizes, and serves from a single file. It is not a high-throughput GPU serving engine like vLLM — see When not to use modelc.
Quickstart
# 1. Build & install the CLI (Rust toolchain required)
# 2. Package a model into a single .modelc artifact
# 3. Serve an OpenAI-compatible API on http://localhost:8080
# 4. Chat with it (OpenAI protocol)
Or run a prebuilt binary directly:
Supported formats
| Format | Flag (-f) |
Extensions | Notes |
|---|---|---|---|
| GGUF | gguf |
.gguf, .bin (sniffed) |
F32/F16/BF16/I8/I16/I32/I64/F64 + Q4_0/Q5_0/Q8_0/Q4_K/Q6_K (dequantized on-the-fly). Reads BPE vocab, merges, BOS/EOS, and Jinja2 chat template from KV metadata. |
| Safetensors | safetensors |
.safetensors |
Merges __metadata__ (architecture / model_type) into the model IR. |
| ONNX | onnx |
.onnx |
Inline, external-data, and segmented initializers; graph ops executed via the tensor runtime. |
| PyTorch | pytorch |
.pt, .pth |
Torch ZIP containers incl. nested Safetensors payloads. Pickle-only checkpoints should be exported to Safetensors/ONNX/GGUF first. |
Ambiguous or extensionless files (e.g. generic .bin) are auto-sniffed against GGUF / zip / Safetensors magic bytes.
Usage
Core workflow
# Package
# Serve
# Local model store (Ollama-style)
Inspect & export
Compile to a standalone binary (legacy)
The compile path and the run path produce bit-identical logits — they share one SIMD GEMV kernel (src/runtime/gemv.rs), so you can develop with run and ship compile and get the same numbers.
Flags reference
pack flags
--compress— zstd-compress the artifact (version-2 format).--quantize fp16|int8|int4— quantize F32 tensors at pack time (INT4/INT8 auto-dequantize onrun).--prune <threshold>— zero small weights below threshold.--arch llama|gpt2|mlp|…— optional architecture hint.--format/-f— force the input weight format.-o— output.modelcpath.
run flags
| Flag | Default | Purpose |
|---|---|---|
--bind / --port |
0.0.0.0 / 8080 |
HTTP bind address / port |
--api-key <key> |
— | Require Authorization: Bearer <key> (exempts /health, /info) |
--rate-limit <N> |
unlimited | Max requests/min per client IP |
--max-concurrent <N> |
unlimited | In-flight inference cap; over-limit gets 503 |
--max-context <N> |
— | Hard context limit → sliding-window KV eviction |
--anchor-tokens <N> |
0 |
Preserve first N tokens during shifts (StreamingLLM) |
--max-tokens <N> |
256 |
Max tokens to generate |
--temperature <T> |
1.0 |
Sampling temperature |
--top-p <P> |
0.0 |
Nucleus sampling |
--min-p <P> |
0.0 |
Min-p sampling |
--repetition-penalty / --presence-penalty / --frequency-penalty |
1.0 / 0.0 / 0.0 |
Token-reuse penalties |
--grammar <regex> |
— | Regex grammar constraint during sampling |
--seed <n> |
— | Deterministic sampling |
--profile |
— | Print per-step inference timing |
compile flags
The emitted model-serve binary binds --bind+--port unless --listen ADDR:PORT is set (wins, embedded verbatim; IPv6 like [::1]:8080 supported). Other flags: --arch, --format/-f, --target (passed to cargo build --target), --debug (build with debug profile instead of --release).
HTTP API
All responses are application/json; streaming endpoints use SSE (text/event-stream).
| Method | Path | Description |
|---|---|---|
GET |
/info |
Model name, architecture, params, bytes, tensor names |
GET |
/health |
Liveness probe |
GET |
/metrics |
Prometheus text (request count, latency histogram, active gauge, tokens counter) |
GET |
/props |
llama.cpp-compatible server properties |
GET |
/api/version |
{ version, git_sha } |
GET |
/v1/system |
CPU/GPU/memory/core info |
GET |
/v1/models · /v1/models/:id |
OpenAI model list / retrieval |
POST |
/infer |
Raw tensor inference (input or batch inputs) |
POST |
/tokenize · /detokenize |
Token ID ↔ text round-trip |
POST |
/chat · /complete |
Native chat / completion |
POST |
/chat/stream |
SSE chat stream |
POST |
/embeddings |
Native embeddings (single or batch) |
POST |
/v1/chat/completions |
OpenAI chat (stream + non-stream, tools, logprobs, JSON schema, n, …) |
POST |
/v1/completions |
OpenAI legacy completion (stream + non-stream, echo, best_of, suffix, n, …) |
POST |
/v1/embeddings |
OpenAI embeddings (encoding_format float/base64, dimensions, usage) |
POST |
/lora/load · /lora/unload |
Hot-swap LoRA adapters without restart |
Inference backend priority: ONNX execution plan → transformer forward (gpt2/llama) → MLP GEMV → echo fallback. Inputs longer than the hidden size are gracefully truncated.
Features
Inference & generation
- GPT-2 / LLaMA transformer runtime — full FP32 forward (layer norm / RMS norm, GeLU / SwiGLU, RoPE, single-token causal attention, output projection), numerically matched to the emitted
compileserver. - Autoregressive generation — greedy, temperature, top-p (nucleus), min-p sampling; repetition / presence / frequency penalties; logit bias; stop sequences; deterministic
--seed. - KV cache — per-layer K/V with FP32, INT8 (~4× smaller), and Mixed (hot FP32 + cold INT8) variants; sliding-window context shifting + StreamingLLM anchor-token preservation.
- Prefix caching — LRU of KV snapshots keyed by token sequence; longest-prefix reuse across requests.
- Speculative decoding — EAGLE3-style
MlpDraftModel(tiny 2-layer MLP) or weight-freePromptLookupDraftModel; verified via the KV cache. - Streaming — SSE token streaming with request cancellation on client disconnect.
- Concurrent generation —
RwLockprefix cache with short hold times; transformer requests run in parallel up to CPU core count.
Quantization & optimization
- Pack-time quantization —
--quantize fp16|int8|int4; preview withinspect --quant-sizes. - Weight pruning —
--prune <threshold>. - GGUF quantized inference — Q4_0/Q5_0/Q8_0/Q4_K/Q6_K preserved in-IR and dequantized on-the-fly (~8× artifact size reduction for Q4_0).
- zstd compression —
--compress(version-2 artifact format). - SIMD GEMV — NEON (aarch64) / AVX (x86_64) K-dimension vectorized dot product, shared bit-for-bit between
runandcompileviasrc/runtime/gemv.rs.
Serving & operations
- OpenAI compatibility —
/v1/chat/completions,/v1/completions,/v1/embeddings,/v1/models; tools, tool_choice, JSON mode / JSON Schema,n,best_of,echo,suffix,logprobs,stream_options.include_usage,user,max_completion_tokens,response_format. - Auth & limits —
--api-keybearer auth,--rate-limitper-IP token bucket,--max-concurrentbackpressure. - Observability — Prometheus
/metrics,/v1/systemhardware info,/api/version,/props. - LoRA hot-swap — load/unload adapters at runtime via HTTP, atomic runtime swap.
- Graceful shutdown — drains in-flight requests (incl. SSE) on
SIGINT/SIGTERM. - Docker/OCI —
modelc containerizeemits a minimal Dockerfile + entrypoint.
Examples
Runnable programs live under examples/ (see examples/README.md):
When not to use modelc
modelc trades flexibility for portability. Prefer raw weight files when you need:
- Rapid A/B swaps without repackaging.
- Very large checkpoints where embedding everything into one artifact is wasteful.
- Multitenant "one server, many paths" deployments that assume on-disk layouts.
- Ecosystems that expect on-disk formats (mmap loaders, GGUF loaders, ONNX Runtime with external weights, HuggingFace
transformers).
See SPEC.md for full scope and limits.
FAQ
Is modelc an Ollama replacement?
It overlaps Ollama's pack/run/list UX and exposes the same OpenAI API, but modelc focuses on single-file packaging and portable artifacts rather than a model zoo. Use Ollama for its catalog and chat app; use modelc when you want one optimized file you can ship anywhere.
Does modelc need a GPU? No. It runs on CPU everywhere (AVX/NEON SIMD). On Apple Silicon it additionally uses Metal. There is no CUDA backend — for GPU high-throughput serving use vLLM.
Which model architectures work end-to-end? GPT-2 and LLaMA run full transformer forward passes with text generation. MLP models run stacked GEMV. ONNX models execute their graph ops. Other architectures parse and inspect correctly but fall back to echo output for generation.
Is the API really OpenAI-compatible?
Yes — /v1/chat/completions, /v1/completions, /v1/embeddings, and /v1/models follow the OpenAI schema (streaming, tools, logprobs, n, JSON schema, etc.), so drop-in clients work. Behavior is bounded by the loaded model's quality.
How is run different from compile?
run serves an existing .modelc artifact in-process. compile emits a standalone pure-Rust binary (cargo build). Both share the same SIMD GEMV kernel and produce identical logits, so run is for iteration and compile is for shipping.
Configuration
A config file at ~/.modelc/config.toml can set default bind, port, store path, and compression. Shell completions: modelc completions bash|zsh|fish. modelc --help and modelc --version list subcommands and the semver + git SHA (from build.rs when .git is present).
Repository layout
src/
bin/modelc.rs CLI entrypoint
cli.rs clap commands, format detection, completions
model.rs canonical Model / TensorData / DataType IR
parsers/ gguf/ onnx/ safetensors.rs pytorch.rs (WeightParser impls)
onnx_exec/ ONNX graph execution engine
codegen/native/ standalone-binary emitter (forward.rs, helpers.rs)
runtime/ tensor/ops/serve + transformer.rs (KV cache) + gemv.rs (shared SIMD)
serve/ axum server: handlers, infer, openai (v1/*), auth, metrics
generate.rs autoregressive generation, samplers, speculative decoding
tokenizer.rs byte-level BPE tokenizer
lora.rs prefix_cache.rs constraint.rs draft.rs ...
examples/ runnable cargo examples
tests/ integration tests
Companion docs: SPEC.md · ARCHITECTURE.md · TODO.md · PRODUCT.md
License
Licensed under the Apache License, Version 2.0 (LICENSE).