diff --git a/docker/hf_pull.sh b/docker/hf_pull.sh new file mode 100755 index 0000000000..77a7d0752b --- /dev/null +++ b/docker/hf_pull.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# Simulate `docker pull ` against a Hugging Face Hub model repo. +# +# Counterpart to docker/hf_push.sh -- downloads the tar.gz blob from the HF +# repo and `docker load`s it. +# +# Usage: +# bash docker/hf_pull.sh [] [] +# bash docker/hf_pull.sh danielhanchen/unsloth-blackwell-docker unsloth-blackwell-test.tar.gz unsloth-blackwell:test +# +# Requires: docker, pigz (or gzip), huggingface-cli logged in (read scope is +# sufficient for public repos). +set -euo pipefail + +REPO="${1:?usage: hf_pull.sh [] []}" +BLOB="${2:-unsloth-blackwell.tar.gz}" +VERIFY="${3:-}" +WORK="${HF_PULL_TMP:-/tmp}" + +command -v docker >/dev/null || { echo "ERROR: docker not on PATH"; exit 1; } +command -v huggingface-cli >/dev/null || { echo "ERROR: huggingface-cli not on PATH (pip install -U huggingface_hub)"; exit 1; } +DECOMPRESSOR=$(command -v pigz || command -v gzip) || { echo "ERROR: need pigz or gzip"; exit 1; } + +DEST="${WORK}/$(basename "${BLOB}")" +echo ">> downloading ${REPO}/${BLOB} -> ${DEST}" +huggingface-cli download "${REPO}" "${BLOB}" --repo-type=model --local-dir "${WORK}" +ls -lh "${DEST}" + +echo ">> loading into docker (using ${DECOMPRESSOR##*/})" +"${DECOMPRESSOR}" -d -c "${DEST}" | docker load + +if [[ -n "${VERIFY}" ]]; then + if docker image inspect "${VERIFY}" >/dev/null 2>&1; then + echo ">> verified: ${VERIFY} is loaded" + docker image inspect --format 'image_id={{.Id}} size={{.Size}}' "${VERIFY}" + else + echo "WARN: expected tag ${VERIFY} not found after load. docker images:" + docker images + exit 1 + fi +fi diff --git a/docker/hf_push.sh b/docker/hf_push.sh new file mode 100755 index 0000000000..1fda968cc9 --- /dev/null +++ b/docker/hf_push.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Simulate `docker push ` against a Hugging Face Hub model repo. +# +# HF Hub doesn't act as an OCI registry for arbitrary images (only Spaces have +# that). So we approximate the push by: +# 1. docker save | pigz -> single tar.gz blob +# 2. huggingface-cli upload to /{tag}.tar.gz +# +# This is good for cross-host testing where you want one canonical place to +# pull from. For the real release, use Docker Hub or GHCR with `docker push`, +# which gives you layer dedup, manifest negotiation, and standard `docker pull` +# UX -- see .github/workflows/docker-publish.yml in this repo. +# +# Usage: +# bash docker/hf_push.sh +# bash docker/hf_push.sh unsloth-blackwell:test danielhanchen/unsloth-blackwell-docker +# +# Requires: docker, pigz (or gzip), huggingface-cli logged in with a write token. +set -euo pipefail + +IMAGE="${1:?usage: hf_push.sh }" +REPO="${2:?usage: hf_push.sh }" +TAG="${IMAGE##*:}" +NAME="${IMAGE%:*}" +NAME="${NAME##*/}" +BLOB="${NAME}-${TAG}.tar.gz" +WORK="${HF_PUSH_TMP:-/tmp}" + +command -v docker >/dev/null || { echo "ERROR: docker not on PATH"; exit 1; } +command -v huggingface-cli >/dev/null || { echo "ERROR: huggingface-cli not on PATH (pip install -U huggingface_hub)"; exit 1; } +COMPRESSOR=$(command -v pigz || command -v gzip) || { echo "ERROR: need pigz or gzip"; exit 1; } + +OUT="${WORK}/${BLOB}" +echo ">> saving ${IMAGE} -> ${OUT} (using ${COMPRESSOR##*/})" +docker save "${IMAGE}" | "${COMPRESSOR}" > "${OUT}" +ls -lh "${OUT}" + +echo ">> uploading to https://huggingface.co/${REPO}/blob/main/${BLOB}" +huggingface-cli upload "${REPO}" "${OUT}" "${BLOB}" \ + --repo-type=model \ + --commit-message="push ${IMAGE} ($(docker inspect --format '{{.Id}}' "${IMAGE}" | cut -c8-19))" + +echo ">> pushed." +echo "On the consumer side, run:" +echo " bash docker/hf_pull.sh ${REPO} ${BLOB} ${IMAGE}"