← Back to blog Engineering

Publishing ML Models as Bundles with xybrid-pack

A packaging pipeline that turns raw model files into versioned, checksummed, cross-platform bundles hosted on HuggingFace. No more 'download the model and put it here.'

Glenn Sonna
· · 5 min read
machinelearningdevopsopensourceai

You’ve trained a model. It works on your machine. Now you need to get it to users.

The typical approach: upload the ONNX file somewhere, write a README explaining which files to download, what preprocessing is needed, and hope people follow the instructions.

We built something better: a packaging pipeline that produces self-contained, versioned, checksummed bundles that any Xybrid client can download and run automatically.


The Problem with Raw Model Distribution

A typical model “distribution” looks like this:

huggingface.co/org/my-model/
├── model.onnx          (330MB)
├── vocab.json          (1.2MB)
├── tokens.txt          (45KB)
├── voices.bin          (12MB)
├── config.json         (2KB)
└── README.md           ("download all files to the same directory")

Problems:

  1. No single download — users must fetch 5 files individually
  2. No integrity checks — how do you know the files aren’t corrupted?
  3. No metadata — which files are required vs. optional?
  4. No versioning — updating one file can break compatibility
  5. No platform variants — Android might need a different quantization than desktop

The .xyb Bundle Format

Xybrid packages models as .xyb bundles: tar + zstd compression with a manifest.json:

kokoro-82m-universal.xyb (compressed)
├── manifest.json
├── model_metadata.json
├── model.onnx
├── tokens.txt
├── voices.bin
└── README.md

The manifest declares contents, checksums, and compatibility:

{
  "model_id": "kokoro-82m",
  "variant": "universal",
  "version": "1.0",
  "files": [
    { "path": "model.onnx", "sha256": "a1b2c3...", "size": 345678901 },
    { "path": "tokens.txt", "sha256": "d4e5f6...", "size": 45123 },
    { "path": "voices.bin", "sha256": "g7h8i9...", "size": 12345678 }
  ],
  "platforms": ["android", "ios", "macos", "linux", "windows"],
  "format": "onnx",
  "created_at": "2026-03-15T10:30:00Z"
}

One download, verified integrity, self-describing contents.

The xybrid-pack Pipeline

xybrid-pack is our model packaging tool. It takes a model spec (YAML) and produces a published, registered bundle.

Step 1: Write a Model Spec

# model-specs/tts/kokoro-82m.yaml
version: "1.0"
status: ready

model:
  id: "kokoro-82m"
  family: "kokoro"
  task: "text-to-speech"
  description: "High-quality multi-voice TTS, 82M parameters"
  license: "Apache-2.0"
  parameters: 82000000

source:
  type: huggingface
  repo: "hexgrad/Kokoro-82M"

files:
  - source: "kokoro-v0_19.onnx"
    target: "model.onnx"
    required: true
  - source: "voices.bin"
    required: true
  - source: "tokens.txt"
    required: true

execution:
  template:
    type: SimpleMode
    model_file: "model.onnx"
  preprocessing:
    - type: Phonemize
      backend: MisakiDictionary
      tokens_file: "tokens.txt"
  postprocessing:
    - type: TTSAudioEncode
      sample_rate: 24000
      apply_postprocessing: true

variants:
  - id: "universal"
    default: true
    format: onnx
    platforms: [android, ios, macos, linux, windows]

publish:
  hf_org: "xybrid-ai"
  hf_repo: "kokoro-82m"

The spec is the single source of truth for how this model is fetched, packaged, and published.

Step 2: Dry Run

just pack-pipeline-dry model-specs/tts/kokoro-82m.yaml

This runs the full pipeline without publishing:

[1/7] Validate ✅  Spec is valid
[2/7] Fetch    ✅  Downloaded 3 files from hexgrad/Kokoro-82M
[3/7] Generate ✅  Created model_metadata.json
[4/7] Build    ✅  Built kokoro-82m-universal.xyb (156MB → 142MB compressed)
[5/7] Publish  ⏭️  Skipped (dry run)
[6/7] Verify   ⏭️  Skipped (dry run)
[7/7] Register ⏭️  Skipped (dry run)

Step 3: Publish

HF_TOKEN=hf_xxx just pack-pipeline model-specs/tts/kokoro-82m.yaml

The full pipeline:

  1. Validate — checks the spec against the schema
  2. Fetch — downloads source files from HuggingFace (or GitHub, or URL)
  3. Generate — creates model_metadata.json from the spec’s execution config
  4. Build — creates the .xyb bundle (tar + zstd)
  5. Publish — uploads to HuggingFace under xybrid-ai/kokoro-82m
  6. Verify — re-downloads the bundle and checks SHA256 matches
  7. Register — adds the model to registry.json with download URL and checksum

Step 4: Verify

xybrid models list
# kokoro-82m  text-to-speech  82M  Apache-2.0

xybrid models info kokoro-82m
# ID:       kokoro-82m
# Task:     text-to-speech
# Variants: universal (onnx, 142MB)
# Platforms: android, ios, macos, linux, windows

Users can now xybrid fetch kokoro-82m and the SDK handles everything.

The Registry

registry.json is a flat index of all published models:

{
  "kokoro-82m": {
    "model_id": "kokoro-82m",
    "task": "text-to-speech",
    "variants": {
      "universal": {
        "url": "https://huggingface.co/xybrid-ai/kokoro-82m/resolve/main/kokoro-82m-universal.xyb",
        "sha256": "abc123...",
        "size": 148897234,
        "platforms": ["android", "ios", "macos", "linux", "windows"]
      }
    }
  }
}

Critical rule: registry.json is never edited manually. Only xybrid-pack register or xybrid-pack pipeline writes to it. This ensures the SHA256 always matches what HuggingFace actually serves.

The pipeline order is intentional: publish → verify → register. We upload first, then re-download and hash, then record. This catches upload corruption.

Platform Variants

Some models need different formats per platform. GGUF for LLMs, ONNX for everything else. Spec supports multiple variants:

variants:
  - id: "universal"
    default: true
    format: onnx
    platforms: [android, ios, macos, linux, windows]

  - id: "q4-mobile"
    format: onnx
    quantization: q4
    source_file: "model-q4.onnx"
    platforms: [android, ios]

The SDK resolves the best variant for the current platform automatically.

Why HuggingFace?

We host bundles on HuggingFace because:

  1. Free hosting for open-source models
  2. CDN — fast downloads globally
  3. Versioning via git LFS
  4. Community — users can browse models on the HF website

The .xyb format is HF-agnostic though. The registry just needs a URL and SHA256. You could host on S3, GCS, or your own CDN.

Model Spec as the Source of Truth

The model spec YAML drives everything:

  • What to download (source files, source repo)
  • How to package (which files, compression)
  • How to run (execution template, pre/post processing)
  • Where to publish (HF org, repo name)
  • What platforms (variant → platform mapping)

Change the spec, re-run the pipeline, and the entire distribution updates consistently.


Quick Reference

# List all model specs
just pack-list

# Validate a spec
just pack-validate model-specs/tts/kokoro-82m.yaml

# Show spec details
just pack-info model-specs/tts/kokoro-82m.yaml

# Dry run (no publish)
just pack-pipeline-dry model-specs/tts/kokoro-82m.yaml

# Full pipeline
HF_TOKEN=hf_xxx just pack-pipeline model-specs/tts/kokoro-82m.yaml

Explore the specs: github.com/xybrid-ai/xybrid

If you’re distributing ML models and tired of “download these 5 files and put them in this directory” — this pattern might help.


How do you distribute your models? Let us know in the comments.

Related articles

· 6 min read

model_metadata.json: A Declarative Schema for ML Model Execution

How a single JSON file replaces custom inference scripts and makes any ML model self-describing. No more 'works on my machine.'

machinelearningarchitectureopensource
· 7 min read

Vendoring llama.cpp in a Rust Workspace (Lessons Learned)

The practical pain of embedding a C++ inference engine in a Rust monorepo — cross-compilation, Android fp16, think-tag stripping, and when to give up on upstream.

rustcppai
· 7 min read

Building a Cross-Platform ML Inference SDK in Rust

How we built a single Rust core that powers ML inference across CLI, Flutter, Swift, Kotlin, and Unity — and the architectural decisions that made it possible.

rustaiarchitecture