Publish Kimodo GGUF bundle workflow
This commit is contained in:
parent
2558baec65
commit
e3eb09fec6
36
README.md
36
README.md
@ -51,7 +51,7 @@ translations and local XYZW rotations.
|
|||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
After building the debug preset and converting the text bundle:
|
After building the debug preset and downloading the native GGUF bundle:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go run ./demo -addr 0.0.0.0:8094
|
go run ./demo -addr 0.0.0.0:8094
|
||||||
@ -61,11 +61,28 @@ Open `http://localhost:8094`. The left sidebar contains the prompt and a
|
|||||||
persistent history; choosing a previous animation restores its prompt for a
|
persistent history; choosing a previous animation restores its prompt for a
|
||||||
new generation.
|
new generation.
|
||||||
|
|
||||||
## Licensed weights
|
## Weights
|
||||||
|
|
||||||
The SMPL-X checkpoint and Llama base model are gated. After accepting their
|
The ready-to-run native GGUF bundle is published under the Hugging Face
|
||||||
Hugging Face licences and authenticating, download the exact revisions and
|
`LocalAI-io` organisation (not GitHub's `localai-org`). Download it directly;
|
||||||
hash manifests with:
|
this avoids recreating the conversion locally:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix develop path:. --command scripts/download_gguf_weights.sh --output "$PWD"
|
||||||
|
```
|
||||||
|
|
||||||
|
The installer verifies the published manifest and SHA-256 hashes. Use
|
||||||
|
`--motion-only` when supplying a precomputed 4096-float LLM2Vec embedding.
|
||||||
|
|
||||||
|
The GGUF bundle includes converted Meta Llama 3 material and Kimodo is
|
||||||
|
non-commercial research-only. Review the published model card and upstream
|
||||||
|
licences before downloading or redistributing.
|
||||||
|
|
||||||
|
### Regenerating the bundle
|
||||||
|
|
||||||
|
This is only needed to reproduce a conversion. The SMPL-X checkpoint and Llama
|
||||||
|
base model are gated. After accepting their Hugging Face licences and
|
||||||
|
authenticating, download the exact revisions and hash manifests with:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nix develop path:. --command hf auth login
|
nix develop path:. --command hf auth login
|
||||||
@ -79,3 +96,12 @@ Convert the local LLM2Vec model to the native component bundle with:
|
|||||||
nix develop path:. --command scripts/convert_llm2vec_bundle.sh \
|
nix develop path:. --command scripts/convert_llm2vec_bundle.sh \
|
||||||
"$PWD/models/llama3-8b-instruct-base" "$PWD/generated/llm2vec-text-bundle"
|
"$PWD/models/llama3-8b-instruct-base" "$PWD/generated/llm2vec-text-bundle"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Validate a prospective release without network access, then explicitly upload
|
||||||
|
it from an account allowed to publish to `LocalAI-io`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix develop path:. --command python scripts/publish_gguf.py
|
||||||
|
nix develop path:. --command python scripts/publish_gguf.py \
|
||||||
|
--upload --confirm-upstream-licences
|
||||||
|
```
|
||||||
|
|||||||
68
scripts/download_gguf_weights.sh
Executable file
68
scripts/download_gguf_weights.sh
Executable file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Download the published native GGUF bundle into Kimodo's standard paths.
|
||||||
|
set -euo pipefail
|
||||||
|
export HF_HUB_DISABLE_PROGRESS_BARS=1
|
||||||
|
|
||||||
|
ORG="${GGUF_ORG:-LocalAI-io}"
|
||||||
|
REPO_DEFAULT="$ORG/Llama-3-Kimodo-SMPLX-RP-v1-GGUF"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
printf '%s\n' "usage: $0 --output DIR [--repo HF_REPO] [--revision REVISION] [--motion-only]" >&2
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
|
||||||
|
output='' repo="$REPO_DEFAULT" revision='main' motion_only=0
|
||||||
|
while [ "$#" -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
--output) [ "$#" -ge 2 ] || usage; output=$2; shift 2 ;;
|
||||||
|
--repo) [ "$#" -ge 2 ] || usage; repo=$2; shift 2 ;;
|
||||||
|
--revision) [ "$#" -ge 2 ] || usage; revision=$2; shift 2 ;;
|
||||||
|
--motion-only) motion_only=1; shift ;;
|
||||||
|
*) usage ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
[ -n "$output" ] || usage
|
||||||
|
command -v hf >/dev/null || { echo "hf not found; enter the Nix shell first" >&2; exit 1; }
|
||||||
|
|
||||||
|
mkdir -p "$output"
|
||||||
|
patterns=("MANIFEST.json" "SHA256SUMS" "models/kimodo-smplx-rp-v1-f32.gguf")
|
||||||
|
if [ "$motion_only" -eq 0 ]; then
|
||||||
|
patterns+=("generated/llm2vec-text-bundle/*")
|
||||||
|
fi
|
||||||
|
echo "Downloading $repo at $revision into $output"
|
||||||
|
args=(download "$repo" --revision "$revision" --local-dir "$output")
|
||||||
|
for pattern in "${patterns[@]}"; do args+=(--include "$pattern"); done
|
||||||
|
hf "${args[@]}" >/dev/null
|
||||||
|
|
||||||
|
manifest="$output/MANIFEST.json"
|
||||||
|
[ -f "$manifest" ] || { echo "missing MANIFEST.json from $repo" >&2; exit 1; }
|
||||||
|
python - "$manifest" "$output" "$motion_only" <<'PY'
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
manifest_path = Path(sys.argv[1])
|
||||||
|
output = Path(sys.argv[2])
|
||||||
|
motion_only = sys.argv[3] == "1"
|
||||||
|
root = output.resolve()
|
||||||
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||||
|
if manifest.get("format") != "kimodo-gguf-manifest-v1":
|
||||||
|
raise SystemExit("unsupported or malformed GGUF manifest")
|
||||||
|
for entry in manifest.get("files", []):
|
||||||
|
relative = Path(entry.get("path", ""))
|
||||||
|
if relative.is_absolute() or ".." in relative.parts or relative.suffix != ".gguf":
|
||||||
|
raise SystemExit(f"unsafe manifest path: {relative}")
|
||||||
|
if motion_only and str(relative).startswith("generated/"):
|
||||||
|
continue
|
||||||
|
path = root / relative
|
||||||
|
if not path.is_file() or path.stat().st_size != entry.get("bytes"):
|
||||||
|
raise SystemExit(f"missing or wrong-sized file: {relative}")
|
||||||
|
h = hashlib.sha256()
|
||||||
|
with path.open("rb") as f:
|
||||||
|
for chunk in iter(lambda: f.read(1 << 20), b""):
|
||||||
|
h.update(chunk)
|
||||||
|
if h.hexdigest() != entry.get("sha256"):
|
||||||
|
raise SystemExit(f"checksum mismatch: {relative}")
|
||||||
|
print("verified native Kimodo GGUF bundle")
|
||||||
|
PY
|
||||||
7
scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/NOTICE
Normal file
7
scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/NOTICE
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Meta Llama 3 is licensed under the Meta Llama 3 Community License, Copyright © Meta Platforms, Inc. All Rights Reserved.
|
||||||
|
|
||||||
|
Built with Meta Llama 3.
|
||||||
|
|
||||||
|
Kimodo-SMPLX-RP-v1 source model: NVIDIA. This converted distribution remains
|
||||||
|
subject to the NVIDIA Internal Scientific Research and Development Model License
|
||||||
|
and is for non-commercial research use only.
|
||||||
66
scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/README.md
Normal file
66
scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/README.md
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
license: other
|
||||||
|
library_name: ggml
|
||||||
|
tags:
|
||||||
|
- gguf
|
||||||
|
- ggml
|
||||||
|
- text-to-motion
|
||||||
|
- smplx
|
||||||
|
- llama-3
|
||||||
|
---
|
||||||
|
|
||||||
|
# Llama-3-Kimodo-SMPLX-RP-v1-GGUF
|
||||||
|
|
||||||
|
Native F32 GGUF conversion of NVIDIA's Kimodo-SMPLX-RP-v1 motion model and
|
||||||
|
its LLM2Vec text encoder. It is for the `kimodo.cpp` GGML runtime; it is not a
|
||||||
|
llama.cpp language-model conversion.
|
||||||
|
|
||||||
|
`Kimodo-SMPLX-RP-v1` is restricted to non-commercial research use. Before use
|
||||||
|
or redistribution, review and comply with the upstream NVIDIA model terms and
|
||||||
|
the Meta Llama 3 Community License below. This repository does not grant rights
|
||||||
|
beyond those upstream licences.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
From the kimodo.cpp checkout:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nix develop path:. --command scripts/download_gguf_weights.sh --output "$PWD"
|
||||||
|
```
|
||||||
|
|
||||||
|
This places the motion model at `models/kimodo-smplx-rp-v1-f32.gguf` and text
|
||||||
|
components under `generated/llm2vec-text-bundle/`, which are the default paths
|
||||||
|
used by the library and demo. Add `--motion-only` for embedding-only inference.
|
||||||
|
`SHA256SUMS` and `MANIFEST.json` record every published artifact and its source
|
||||||
|
revision; the installer verifies the selected files after download.
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
- `models/kimodo-smplx-rp-v1-f32.gguf` — Kimodo motion diffusion model (F32)
|
||||||
|
- `generated/llm2vec-text-bundle/` — tokenizer, embeddings, final norm, and 32
|
||||||
|
F32 transformer layers for native LLM2Vec inference
|
||||||
|
|
||||||
|
The text components are split deliberately: kimodo.cpp loads a bounded number
|
||||||
|
of layers at once for GPU memory control.
|
||||||
|
|
||||||
|
## Provenance
|
||||||
|
|
||||||
|
The conversion is generated by `kimodo.cpp` from these exact upstream commits:
|
||||||
|
|
||||||
|
- NVIDIA Kimodo-SMPLX-RP-v1: `1419ba56b734c48bbafb41fefa84088ca94583b5`
|
||||||
|
- Meta Llama-3-8B-Instruct: `8afb486c1db24fe5011ec46dfbe5b5dccdb575c2`
|
||||||
|
- McGill LLM2Vec MNTP adapter: `31474e395ada192e8ed1586db6be79fb3b70c9c0`
|
||||||
|
- McGill LLM2Vec supervised adapter: `baa8ebf04a1c2500e61288e7dad65e8ae42601a7`
|
||||||
|
|
||||||
|
The two McGill adapters are MIT-licensed. The merged text encoder includes
|
||||||
|
Meta Llama 3 material. **Built with Meta Llama 3.**
|
||||||
|
|
||||||
|
## Licences and notices
|
||||||
|
|
||||||
|
- Kimodo: [NVIDIA Internal Scientific Research and Development Model License](https://huggingface.co/nvidia/Kimodo-SMPLX-RP-v1)
|
||||||
|
(non-commercial research only).
|
||||||
|
- Text base: [Meta Llama 3 Community License](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct).
|
||||||
|
- LLM2Vec adapters: [MIT](https://huggingface.co/McGill-NLP/LLM2Vec-Meta-Llama-3-8B-Instruct-mntp).
|
||||||
|
|
||||||
|
`LICENSE-META-LLAMA-3.txt` and `NOTICE` accompany every published copy with
|
||||||
|
the required Meta licence and attribution.
|
||||||
147
scripts/publish_gguf.py
Executable file
147
scripts/publish_gguf.py
Executable file
@ -0,0 +1,147 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Publish the reproducible Kimodo GGUF distribution to Hugging Face.
|
||||||
|
|
||||||
|
The default is deliberately a dry run: it validates the exact converter
|
||||||
|
outputs, prints every path, size and SHA-256, and performs no network I/O.
|
||||||
|
Use --upload only after reviewing the upstream licence obligations.
|
||||||
|
|
||||||
|
The text bundle contains merged Meta Llama 3 weights. It is therefore kept in
|
||||||
|
the same repository as the Kimodo motion GGUF and is published under the model
|
||||||
|
name required by the Meta Llama 3 Community License:
|
||||||
|
|
||||||
|
LocalAI-io/Llama-3-Kimodo-SMPLX-RP-v1-GGUF
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
HF_ORG = "LocalAI-io" # Hugging Face organisation; GitHub is localai-org.
|
||||||
|
DEFAULT_REPO = f"{HF_ORG}/Llama-3-Kimodo-SMPLX-RP-v1-GGUF"
|
||||||
|
MOTION_NAME = "kimodo-smplx-rp-v1-f32.gguf"
|
||||||
|
TEXT_NAMES = (
|
||||||
|
"tokenizer.gguf", "embedding.gguf", "final-norm.gguf",
|
||||||
|
*(f"layer-{index:02d}.gguf" for index in range(32)),
|
||||||
|
)
|
||||||
|
SOURCE_REVISIONS = {
|
||||||
|
"nvidia/Kimodo-SMPLX-RP-v1": "1419ba56b734c48bbafb41fefa84088ca94583b5",
|
||||||
|
"meta-llama/Meta-Llama-3-8B-Instruct": "8afb486c1db24fe5011ec46dfbe5b5dccdb575c2",
|
||||||
|
"McGill-NLP/LLM2Vec-Meta-Llama-3-8B-Instruct-mntp": "31474e395ada192e8ed1586db6be79fb3b70c9c0",
|
||||||
|
"McGill-NLP/LLM2Vec-Meta-Llama-3-8B-Instruct-mntp-supervised": "baa8ebf04a1c2500e61288e7dad65e8ae42601a7",
|
||||||
|
}
|
||||||
|
CARD = ROOT / "scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/README.md"
|
||||||
|
NOTICE = ROOT / "scripts/hf/Llama-3-Kimodo-SMPLX-RP-v1-GGUF/NOTICE"
|
||||||
|
LLAMA_LICENSE = ROOT / "models/llama3-8b-instruct-base/LICENSE"
|
||||||
|
|
||||||
|
|
||||||
|
def digest(path: Path) -> str:
|
||||||
|
value = hashlib.sha256()
|
||||||
|
with path.open("rb") as handle:
|
||||||
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||||
|
value.update(chunk)
|
||||||
|
return value.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def require_revision(repo: str) -> None:
|
||||||
|
revision = ROOT / "models" / {
|
||||||
|
"nvidia/Kimodo-SMPLX-RP-v1": "Kimodo-SMPLX-RP-v1",
|
||||||
|
"meta-llama/Meta-Llama-3-8B-Instruct": "llama3-8b-instruct-base",
|
||||||
|
"McGill-NLP/LLM2Vec-Meta-Llama-3-8B-Instruct-mntp": "llm2vec-mntp-adapter",
|
||||||
|
"McGill-NLP/LLM2Vec-Meta-Llama-3-8B-Instruct-mntp-supervised": "llm2vec-adapter",
|
||||||
|
}[repo] / "REVISION"
|
||||||
|
if not revision.is_file():
|
||||||
|
raise ValueError(f"missing provenance file: {revision}")
|
||||||
|
actual = revision.read_text(encoding="utf-8").split()[0]
|
||||||
|
if actual != SOURCE_REVISIONS[repo]:
|
||||||
|
raise ValueError(f"unexpected {repo} revision: {actual} (expected {SOURCE_REVISIONS[repo]})")
|
||||||
|
|
||||||
|
|
||||||
|
def artifacts(motion: Path, bundle: Path) -> list[tuple[Path, str]]:
|
||||||
|
result = [(motion, f"models/{MOTION_NAME}")]
|
||||||
|
result.extend((bundle / name, f"generated/llm2vec-text-bundle/{name}") for name in TEXT_NAMES)
|
||||||
|
for source, destination in result:
|
||||||
|
if not source.is_file() or source.stat().st_size == 0:
|
||||||
|
raise ValueError(f"missing or empty GGUF: {source}")
|
||||||
|
if source.suffix != ".gguf":
|
||||||
|
raise ValueError(f"not a GGUF: {source}")
|
||||||
|
with source.open("rb") as handle:
|
||||||
|
if handle.read(4) != b"GGUF":
|
||||||
|
raise ValueError(f"invalid GGUF magic: {source}")
|
||||||
|
if ".." in Path(destination).parts:
|
||||||
|
raise ValueError(f"unsafe destination: {destination}")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--motion", type=Path, default=ROOT / "models" / MOTION_NAME)
|
||||||
|
parser.add_argument("--text-bundle", type=Path,
|
||||||
|
default=ROOT / "generated/llm2vec-text-bundle")
|
||||||
|
parser.add_argument("--repo", default=DEFAULT_REPO)
|
||||||
|
parser.add_argument("--upload", action="store_true",
|
||||||
|
help="actually create/update the Hugging Face model repo")
|
||||||
|
parser.add_argument("--confirm-upstream-licences", action="store_true",
|
||||||
|
help="required with --upload; confirms authority to redistribute all inputs")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not CARD.is_file() or not NOTICE.is_file() or not LLAMA_LICENSE.is_file():
|
||||||
|
raise ValueError("model card, NOTICE, or Meta Llama 3 licence is missing")
|
||||||
|
for repo in SOURCE_REVISIONS:
|
||||||
|
require_revision(repo)
|
||||||
|
files = artifacts(args.motion, args.text_bundle)
|
||||||
|
except ValueError as error:
|
||||||
|
print(f"error: {error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
entries = []
|
||||||
|
for source, destination in files:
|
||||||
|
entries.append({"path": destination, "bytes": source.stat().st_size, "sha256": digest(source)})
|
||||||
|
manifest = {
|
||||||
|
"format": "kimodo-gguf-manifest-v1",
|
||||||
|
"repository": args.repo,
|
||||||
|
"source_revisions": SOURCE_REVISIONS,
|
||||||
|
"files": entries,
|
||||||
|
}
|
||||||
|
sums = "".join(f"{entry['sha256']} {entry['path']}\n" for entry in entries)
|
||||||
|
total = sum(entry["bytes"] for entry in entries)
|
||||||
|
|
||||||
|
print(f"repo: https://huggingface.co/{args.repo}")
|
||||||
|
print(f"files: {len(entries)} GGUFs, {total / 1e9:.2f} GB")
|
||||||
|
for entry in entries:
|
||||||
|
print(f" {entry['sha256']} {entry['bytes']:>12} {entry['path']}")
|
||||||
|
if not args.upload:
|
||||||
|
print("\n[dry-run] nothing uploaded. Re-run with --upload --confirm-upstream-licences to publish.")
|
||||||
|
return 0
|
||||||
|
if not args.confirm_upstream_licences:
|
||||||
|
print("error: --upload requires --confirm-upstream-licences", file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
from huggingface_hub import HfApi
|
||||||
|
api = HfApi()
|
||||||
|
api.create_repo(args.repo, repo_type="model", exist_ok=True)
|
||||||
|
uploads = [(CARD, "README.md"), (NOTICE, "NOTICE"),
|
||||||
|
(LLAMA_LICENSE, "LICENSE-META-LLAMA-3.txt")]
|
||||||
|
for source, destination in uploads + files:
|
||||||
|
print(f"uploading {destination} ...", flush=True)
|
||||||
|
api.upload_file(path_or_fileobj=str(source), path_in_repo=destination,
|
||||||
|
repo_id=args.repo, repo_type="model",
|
||||||
|
commit_message=f"Add {destination}")
|
||||||
|
for payload, destination in ((json.dumps(manifest, indent=2, sort_keys=True).encode() + b"\n", "MANIFEST.json"),
|
||||||
|
(sums.encode(), "SHA256SUMS")):
|
||||||
|
print(f"uploading {destination} ...", flush=True)
|
||||||
|
api.upload_file(path_or_fileobj=io.BytesIO(payload), path_in_repo=destination,
|
||||||
|
repo_id=args.repo, repo_type="model",
|
||||||
|
commit_message=f"Add {destination}")
|
||||||
|
print(f"done -> https://huggingface.co/{args.repo}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Loading…
Reference in New Issue
Block a user