CoolFace
Apppublic

DGXAI/driftcall

sourceHugging Faceapache-2.0updated 5mo agoView on Hugging Face
0likes
step_24_deploy_hf.py415 linesDownload Raw Back to cells
1"""Cell 24 — Hugging Face Hub + Spaces deployment.2 3Implements ``docs/modules/deploy_env_space.md`` §8.2 and DESIGN.md §11.3, §11.44deliverables. Four push helpers, all using the **new** ``hf upload`` CLI per5deploy_env_space.md §8.2 (deprecated ``huggingface-cli`` is forbidden).6 7Public surface:8  * ``push_lora_to_hub(checkpoint_path, repo_id, token)`` — LoRA-only adapter9    push with ``safe_serialization=True``. Never the naive 4-bit → 16-bit merge10    path (DESIGN.md §10.5, CLAUDE.md §13).11  * ``push_env_space(repo_id, token)`` — Docker-based env Space (CPU basic,12    deploy_env_space.md §6.3).13  * ``push_demo_space(repo_id, token)`` — Demo Space targeting ZeroGPU with14    A10G fallback (deploy_demo_space.md §3.1, §3.7).15  * ``push_dataset(brief_path, repo_id, token)`` — ``driftcall-indic-briefs``16    dataset (DESIGN.md §11.4).17 18All four return a frozen :class:`DeploymentResult` so a caller can audit the19exact ``hf`` invocation. Heavy deps (``huggingface_hub``, ``subprocess`` for20``hf``) are loaded lazily; tests monkeypatch the loaders to assert the21command construction without making network calls.22"""23 24from __future__ import annotations25 26import logging27import subprocess28from dataclasses import dataclass29from pathlib import Path30from typing import TYPE_CHECKING, Any, Literal31 32if TYPE_CHECKING:33    from collections.abc import Callable, Mapping34 35logger = logging.getLogger(__name__)36 37 38# ---------------------------------------------------------------------------39# Constants — repo defaults (DESIGN.md §11.3, §11.4, deploy_*_space.md §3.7)40# ---------------------------------------------------------------------------41 42 43DEFAULT_LORA_REPO_ID: str = "DGXAI/gemma-3n-e2b-driftcall-lora"44DEFAULT_DATASET_REPO_ID: str = "driftcall/driftcall-indic-briefs"45DEFAULT_ENV_SPACE_ID: str = "driftcall/driftcall-env"46DEFAULT_DEMO_SPACE_ID: str = "driftcall/driftcall-demo"47 48RepoType = Literal["model", "dataset", "space"]49 50DEPRECATED_CLI_NAMES: tuple[str, ...] = ("huggingface-cli",)51 52 53# ---------------------------------------------------------------------------54# Errors55# ---------------------------------------------------------------------------56 57 58class DeploymentError(Exception):59    """Root for every typed deploy-cell error."""60 61 62class HFTokenMissingError(DeploymentError):63    """Raised when the ``token`` argument is None or empty."""64 65 66class CheckpointPathMissingError(DeploymentError):67    """Raised when the LoRA checkpoint path does not exist."""68 69 70class NaiveMergeForbiddenError(DeploymentError):71    """Raised when the caller requests a 4-bit → 16-bit merge path72    (CLAUDE.md §13, DESIGN.md §10.5)."""73 74 75class DeploymentCommandError(DeploymentError):76    """Raised when the ``hf upload`` invocation exits non-zero."""77 78 79class DeprecatedCliError(DeploymentError):80    """Raised when a caller would invoke ``huggingface-cli`` instead of ``hf``."""81 82 83# ---------------------------------------------------------------------------84# DeploymentResult85# ---------------------------------------------------------------------------86 87 88@dataclass(frozen=True)89class DeploymentResult:90    """Audit record for one deployment call."""91 92    repo_id: str93    repo_type: RepoType94    command: tuple[str, ...]95    return_code: int96    stdout: str97    stderr: str98    success: bool99 100 101# ---------------------------------------------------------------------------102# Lazy dep loaders — patched by tests103# ---------------------------------------------------------------------------104 105 106def _load_hf_api() -> Any:107    """Return the ``huggingface_hub.HfApi`` class. Patched in tests."""108 109    from huggingface_hub import HfApi110 111    return HfApi112 113 114def _load_subprocess_run() -> Callable[..., Any]:115    """Return ``subprocess.run``. Patched in tests."""116 117    return subprocess.run118 119 120# ---------------------------------------------------------------------------121# Argument validation helpers122# ---------------------------------------------------------------------------123 124 125def _validate_token(token: str | None) -> str:126    if token is None or token.strip() == "":127        raise HFTokenMissingError("token argument is required and must be non-empty")128    return token129 130 131def _validate_repo_id(repo_id: str) -> str:132    if not isinstance(repo_id, str) or "/" not in repo_id:133        raise DeploymentError(f"repo_id must be 'org/name'; got {repo_id!r}")134    org, name = repo_id.split("/", 1)135    if not org or not name:136        raise DeploymentError(f"repo_id must be 'org/name'; got {repo_id!r}")137    return repo_id138 139 140def _validate_path_exists(path: Path, *, label: str) -> Path:141    if not isinstance(path, Path):142        raise DeploymentError(f"{label} must be pathlib.Path; got {type(path).__name__}")143    if not path.exists():144        raise CheckpointPathMissingError(f"{label} not found: {path}")145    return path146 147 148def _ensure_not_deprecated(executable: str) -> str:149    if executable in DEPRECATED_CLI_NAMES:150        raise DeprecatedCliError(151            f"{executable!r} is deprecated; use 'hf upload' (deploy_env_space.md §8.2)",152        )153    return executable154 155 156# ---------------------------------------------------------------------------157# Command construction158# ---------------------------------------------------------------------------159 160 161def build_hf_upload_command(162    *,163    repo_id: str,164    local_path: Path,165    repo_type: RepoType,166    revision: str | None = None,167    extra_args: tuple[str, ...] = (),168) -> tuple[str, ...]:169    """Construct an argv tuple for ``hf upload``.170 171    Shape per the new ``hf`` CLI (deploy_env_space.md §8.2):172        ``hf upload <repo_id> <local_path> --repo-type=<type> [--revision=<r>]``173    """174 175    _validate_repo_id(repo_id)176    if repo_type not in ("model", "dataset", "space"):177        raise DeploymentError(f"repo_type must be model|dataset|space; got {repo_type!r}")178    executable = _ensure_not_deprecated("hf")179    cmd: list[str] = [180        executable,181        "upload",182        repo_id,183        str(local_path),184        f"--repo-type={repo_type}",185    ]186    if revision is not None:187        cmd.append(f"--revision={revision}")188    cmd.extend(extra_args)189    return tuple(cmd)190 191 192def _run_command(193    cmd: tuple[str, ...],194    *,195    token: str,196    env_extra: Mapping[str, str] | None = None,197) -> tuple[int, str, str]:198    """Invoke ``cmd`` via subprocess; return ``(rc, stdout, stderr)``.199 200    The token is passed via environment, never via argv (avoids shell201    history leak). ``env_extra`` lets callers add per-deploy env vars.202    """203 204    import os205 206    run = _load_subprocess_run()207    env = dict(os.environ)208    env["HF_TOKEN"] = token209    env["HUGGINGFACE_HUB_TOKEN"] = token210    if env_extra is not None:211        env.update(env_extra)212    try:213        completed = run(214            list(cmd),215            check=False,216            capture_output=True,217            text=True,218            env=env,219        )220    except FileNotFoundError as exc:221        raise DeploymentCommandError(f"hf CLI not found on PATH: {exc}") from exc222    rc = int(getattr(completed, "returncode", 1))223    stdout = str(getattr(completed, "stdout", "") or "")224    stderr = str(getattr(completed, "stderr", "") or "")225    return rc, stdout, stderr226 227 228# ---------------------------------------------------------------------------229# push_lora_to_hub (DESIGN.md §11.3)230# ---------------------------------------------------------------------------231 232 233def push_lora_to_hub(234    checkpoint_path: Path,235    repo_id: str = DEFAULT_LORA_REPO_ID,236    token: str | None = None,237    *,238    merge_4bit_to_16bit: bool = False,239    revision: str | None = None,240) -> DeploymentResult:241    """Push the LoRA adapter directory to the HF Hub.242 243    Pushes adapter-only artifacts (``adapter_config.json``,244    ``adapter_model.safetensors``, ``tokenizer.json``, ``README.md``).245    Never the merged-fp16 weights — see DESIGN.md §10.5 + CLAUDE.md §13:246    naive 4-bit → 16-bit merging is the catastrophic-quality path.247    """248 249    if merge_4bit_to_16bit:250        raise NaiveMergeForbiddenError(251            "merge_4bit_to_16bit=True is forbidden: 4-bit → 16-bit merge "252            "produces silently broken weights (DESIGN.md §10.5, CLAUDE.md §13). "253            "Push the LoRA adapter only.",254        )255    resolved_token = _validate_token(token)256    _validate_path_exists(checkpoint_path, label="checkpoint_path")257    cmd = build_hf_upload_command(258        repo_id=repo_id,259        local_path=checkpoint_path,260        repo_type="model",261        revision=revision,262    )263    rc, stdout, stderr = _run_command(cmd, token=resolved_token)264    success = rc == 0265    if not success:266        logger.warning("push_lora_to_hub failed (rc=%d): %s", rc, stderr)267    return DeploymentResult(268        repo_id=repo_id,269        repo_type="model",270        command=cmd,271        return_code=rc,272        stdout=stdout,273        stderr=stderr,274        success=success,275    )276 277 278# ---------------------------------------------------------------------------279# push_env_space (deploy_env_space.md §4.4, §6.3)280# ---------------------------------------------------------------------------281 282 283def push_env_space(284    repo_id: str = DEFAULT_ENV_SPACE_ID,285    token: str | None = None,286    *,287    space_dir: Path | None = None,288    revision: str | None = None,289) -> DeploymentResult:290    """Push the env Space (Docker SDK, CPU basic). deploy_env_space.md §4.4."""291 292    resolved_token = _validate_token(token)293    if space_dir is None:294        space_dir = Path(".")295    _validate_path_exists(space_dir, label="space_dir")296    cmd = build_hf_upload_command(297        repo_id=repo_id,298        local_path=space_dir,299        repo_type="space",300        revision=revision,301    )302    rc, stdout, stderr = _run_command(cmd, token=resolved_token)303    success = rc == 0304    return DeploymentResult(305        repo_id=repo_id,306        repo_type="space",307        command=cmd,308        return_code=rc,309        stdout=stdout,310        stderr=stderr,311        success=success,312    )313 314 315# ---------------------------------------------------------------------------316# push_demo_space (deploy_demo_space.md §3.1, §3.7)317# ---------------------------------------------------------------------------318 319 320def push_demo_space(321    repo_id: str = DEFAULT_DEMO_SPACE_ID,322    token: str | None = None,323    *,324    space_dir: Path | None = None,325    hardware: Literal["zero-gpu", "a10g-small"] = "zero-gpu",326    revision: str | None = None,327) -> DeploymentResult:328    """Push the demo Space. Default hardware ``zero-gpu`` per329    deploy_demo_space.md §3.1; pass ``a10g-small`` to redeploy on the330    fallback hardware (§3.1 step 2)."""331 332    resolved_token = _validate_token(token)333    if hardware not in ("zero-gpu", "a10g-small"):334        raise DeploymentError(335            f"hardware must be zero-gpu|a10g-small; got {hardware!r}",336        )337    if space_dir is None:338        space_dir = Path(".")339    _validate_path_exists(space_dir, label="space_dir")340    cmd = build_hf_upload_command(341        repo_id=repo_id,342        local_path=space_dir,343        repo_type="space",344        revision=revision,345    )346    env_extra = {"DRIFTCALL_HARDWARE": hardware}347    rc, stdout, stderr = _run_command(cmd, token=resolved_token, env_extra=env_extra)348    success = rc == 0349    return DeploymentResult(350        repo_id=repo_id,351        repo_type="space",352        command=cmd,353        return_code=rc,354        stdout=stdout,355        stderr=stderr,356        success=success,357    )358 359 360# ---------------------------------------------------------------------------361# push_dataset (DESIGN.md §11.4)362# ---------------------------------------------------------------------------363 364 365def push_dataset(366    brief_path: Path,367    repo_id: str = DEFAULT_DATASET_REPO_ID,368    token: str | None = None,369    *,370    revision: str | None = None,371) -> DeploymentResult:372    """Push the ``driftcall-indic-briefs`` dataset (DESIGN.md §11.4)."""373 374    resolved_token = _validate_token(token)375    _validate_path_exists(brief_path, label="brief_path")376    cmd = build_hf_upload_command(377        repo_id=repo_id,378        local_path=brief_path,379        repo_type="dataset",380        revision=revision,381    )382    rc, stdout, stderr = _run_command(cmd, token=resolved_token)383    success = rc == 0384    return DeploymentResult(385        repo_id=repo_id,386        repo_type="dataset",387        command=cmd,388        return_code=rc,389        stdout=stdout,390        stderr=stderr,391        success=success,392    )393 394 395__all__ = [396    "DEFAULT_DATASET_REPO_ID",397    "DEFAULT_DEMO_SPACE_ID",398    "DEFAULT_ENV_SPACE_ID",399    "DEFAULT_LORA_REPO_ID",400    "DEPRECATED_CLI_NAMES",401    "CheckpointPathMissingError",402    "DeploymentCommandError",403    "DeploymentError",404    "DeploymentResult",405    "DeprecatedCliError",406    "HFTokenMissingError",407    "NaiveMergeForbiddenError",408    "RepoType",409    "build_hf_upload_command",410    "push_dataset",411    "push_demo_space",412    "push_env_space",413    "push_lora_to_hub",414]415