This post covers the steps to upload a trained YOLOv5 model (best.pt) to Hugging Face Hub, configure a Model Card, and deploy a Gradio demo to Spaces, along with fixes for several issues encountered in the process.

The model in question detects page layouts in classical Japanese manuscript collections ("Kunshujo").

Demo screen


How the Structure Changed

Before

yolov5-kunshujo/
├── app.py               # Loads model from local disk on startup
├── best.pt              # 699 MB model stored directly in the repo
├── requirements.txt     # Included unnecessary packages (gdown, matplotlib, seaborn)
└── ultralytics/
    └── yolov5/          # YOLOv5 source code copied locally
        ├── hubconf.py
        ├── models/
        └── utils/

Problems:

  • best.pt (699 MB) was inside the Spaces repo and tracked by Git
  • YOLOv5 source code had to be maintained as a local copy
  • Model loaded at startup, making Spaces boot slow
  • Not compatible with PyTorch 2.6 breaking changes

After

yolov5-kunshujo/
├── app.py               # Downloads model from HF Hub; lazy-loads on first inference
└── requirements.txt     # Trimmed to only what's needed

Improvements:

  • best.pt moved to a separate model repository (nakamura196/yolov5-kunshujo)
  • ultralytics/yolov5/ removed; torch.hub fetches and caches it from GitHub
  • Model is lazy-loaded on first inference (faster Spaces startup)
  • PyTorch 2.6 compatibility fix applied
  • Unnecessary packages removed

1. Setting Up huggingface-hub

pip install huggingface_hub

If the hf command is not found after installation, the binary is not on your PATH.

# Run with full path
/Users/nakamura/Library/Python/3.9/bin/hf login

# Or add permanently to PATH
echo 'export PATH="$HOME/Library/Python/3.9/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc

huggingface-cli is deprecated; migrating to the hf command is recommended.

2. Creating a Model Repository

hf repo create yolov5-kunshujo --repo-type model

3. Uploading the Model File

hf upload nakamura196/yolov5-kunshujo ./best.pt best.pt

best.pt is about 700 MB, but Hugging Face's xet storage backend makes the upload fast.

4. Setting Up the Model Card

The README.md in the model repository serves as the Model Card. Create it in the following format and upload it.

---
license: apache-2.0
tags:
  - object-detection
  - yolov5
  - pytorch
pipeline_tag: object-detection
---

# YOLOv5 Kunshujo

...
hf upload nakamura196/yolov5-kunshujo ./README.md README.md

5. Updating the Gradio App

Fixing REPO_ID

Check that app.py does not still contain a placeholder like username/yolov5-kunshujo.

REPO_ID = "nakamura196/yolov5-kunshujo"

Lazy-Loading from HF Hub

Instead of loading at startup, use hf_hub_download to download and load the model on first inference.

from huggingface_hub import hf_hub_download

_model = None

def get_model():
    global _model
    if _model is not None:
        return _model
    model_path = hf_hub_download(repo_id=REPO_ID, filename=MODEL_FILENAME)
    # ... load model
    return _model

On the first call, the model is downloaded from HF; subsequent calls use the cache at ~/.cache/huggingface/hub/.

Cleaning Up requirements.txt

List all packages required by yolov5 v7.0 explicitly.

torch
torchvision
Pillow
opencv-python-headless
ipython
scipy
pandas
matplotlib
seaborn
psutil
PackageReason
torch, torchvisionRequired for YOLOv5 inference
PillowImage processing
opencv-python-headlessutils/dataloaders.py imports cv2
ipythonUsed by utils/general.py
scipy, pandasUsed by various YOLOv5 utilities
matplotlib, seabornUsed by utils/metrics.py, utils/plots.py
psutilUsed by utils/dataloaders.py
huggingface-hubPre-installed in HF Spaces; no need to list

torch.hub.load fetches the yolov5 code from GitHub and resolves its dependencies within what is listed in requirements.txt. If a missing package surfaces at runtime, add it as needed.


Final app.py

# Patch gradio_client bug: additionalProperties: True (bool) causes TypeError/APIInfoParseError
import gradio_client.utils as _gc_utils
_orig_j2p = _gc_utils._json_schema_to_python_type
def _patched_j2p(schema, defs):
    if not isinstance(schema, dict):
        return "any"
    return _orig_j2p(schema, defs)
_gc_utils._json_schema_to_python_type = _patched_j2p

import gradio as gr
import torch
from PIL import Image, ImageDraw
from huggingface_hub import hf_hub_download

REPO_ID = "nakamura196/yolov5-kunshujo"
MODEL_FILENAME = "best.pt"

_model = None


def get_model():
    global _model
    if _model is not None:
        return _model

    model_path = hf_hub_download(repo_id=REPO_ID, filename=MODEL_FILENAME)

    # PyTorch 2.6+ weights_only=True breaking change workaround
    _original_load = torch.load
    torch.load = lambda *a, **kw: _original_load(*a, **{**kw, "weights_only": False})
    try:
        _model = torch.hub.load("ultralytics/yolov5:v7.0", "custom", path=model_path, trust_repo=True)
    finally:
        torch.load = _original_load

    return _model


def yolo(im, size=1024):
    model = get_model()
    g = (size / max(im.size))
    im = im.resize(tuple(int(x * g) for x in im.size), Image.BICUBIC)

    results = model(im)
    res = results.pandas().xyxy[0].to_dict(orient="records")

    im_draw = im.copy()
    draw = ImageDraw.Draw(im_draw)

    detected_images = []
    for item in res:
        xmin, ymin, xmax, ymax = item['xmin'], item['ymin'], item['xmax'], item['ymax']
        draw.rectangle([(xmin, ymin), (xmax, ymax)], outline="red", width=2)
        detected_images.append(im.crop((xmin, ymin, xmax, ymax)))

    return [res, im_draw, detected_images]


inputs = [gr.Image(type='pil', label="Original Image")]
outputs = [
    gr.JSON(label="JSON Output"),
    gr.Image(type="pil", label="Output Image"),
    gr.Gallery(label="Detected Objects"),
]

demo = gr.Interface(
    yolo, inputs, outputs,
    title="YOLOv5 Kunshujo",
    description="YOLOv5 Kunshujo Gradio demo for object detection.",
    examples=[['2586696_R0000008.jpg'], ['2586696_R0000009.jpg']],
    flagging_mode="never",
)

demo.launch(show_error=True, server_name="0.0.0.0", server_port=7860, ssr_mode=False)

Troubleshooting

gradio_client TypeError / APIInfoParseError

Error:

TypeError: argument of type 'bool' is not iterable
# or
gradio_client.utils.APIInfoParseError: Cannot parse schema True

Cause: The schema for gr.JSON or gr.Gallery components contains additionalProperties: True (a boolean), and gradio_client's _json_schema_to_python_type() function crashes when it receives a non-dict value. This causes the /info endpoint to return a 500 error, which Gradio interprets as "cannot connect to localhost," aborting startup.

Fix: Apply a monkey-patch at the top of app.py (before import gradio).

import gradio_client.utils as _gc_utils
_orig_j2p = _gc_utils._json_schema_to_python_type
def _patched_j2p(schema, defs):
    if not isinstance(schema, dict):
        return "any"
    return _orig_j2p(schema, defs)
_gc_utils._json_schema_to_python_type = _patched_j2p

Because _json_schema_to_python_type is resolved in the module's global namespace, recursive calls are also automatically patched.

PyTorch 2.6 weights_only Issue

Error:

Weights only load failed. WeightsUnpickler error: Unsupported global:
GLOBAL numpy.core.multiarray._reconstruct was not an allowed global by default.

Cause: Starting with PyTorch 2.6, torch.load defaults to weights_only=True, which prevents loading older YOLOv5 models that contain numpy objects. Since the flag cannot be passed directly to the torch.load call inside torch.hub.load, a monkey-patch is used instead.

Fix:

_original_load = torch.load
torch.load = lambda *a, **kw: _original_load(*a, **{**kw, "weights_only": False})
try:
    _model = torch.hub.load("ultralytics/yolov5:v7.0", "custom", path=model_path, trust_repo=True)
finally:
    torch.load = _original_load

im.resize() Generator Error

Error:

argument 1 must be 2-item sequence, not generator

Fix:

# Before
im = im.resize((int(x * g) for x in im.size), Image.BICUBIC)

# After
im = im.resize(tuple(int(x * g) for x in im.size), Image.BICUBIC)

Gradio Fails to Start on Python 3.13

Error:

ModuleNotFoundError: No module named 'pyaudioop'

Cause: HF Spaces uses Python 3.13, and the audioop module that gradio 4.x depends on (via pydub) was removed in Python 3.13.

Fix 1: Update sdk_version in README.md to gradio 5.x.

sdk_version: 5.9.1

Fix 2: In gradio 5.x, allow_flagging was removed in favor of flagging_mode.

# Before (gradio 4.x)
demo = gr.Interface(..., allow_flagging="never")

# After (gradio 5.x)
demo = gr.Interface(..., flagging_mode="never")

ModuleNotFoundError: No module named 'ultralytics'

Cause: Using the master branch of torch.hub.load("ultralytics/yolov5", ...) now requires the new ultralytics package as a dependency.

Fix: Pin to v7.0.

_model = torch.hub.load("ultralytics/yolov5:v7.0", "custom", path=model_path, trust_repo=True)

Note: best.pt (trained with the old ultralytics/yolov5 repo) cannot be loaded with the new ultralytics package (from ultralytics import YOLO) because the architecture is different (anchor-free). For old models, torch.hub pinned to v7.0 is the only recommended approach.

EOFError: EOF when reading a line (trust_repo)

Cause: In headless environments (Spaces, CI), torch.hub.load tries to display a trust confirmation prompt and fails.

Fix: Add trust_repo=True.

_model = torch.hub.load("ultralytics/yolov5:v7.0", "custom", path=model_path, trust_repo=True)

Missing Dependencies for yolov5 v7.0

Example errors:

ModuleNotFoundError: No module named 'cv2'
ModuleNotFoundError: No module named 'IPython'
ModuleNotFoundError: No module named 'matplotlib'
ModuleNotFoundError: No module named 'seaborn'
ModuleNotFoundError: No module named 'psutil'

Cause: yolov5 v7.0 depends on many packages, and torch.hub.load does not install them automatically.

Fix: List all required packages in requirements.txt (see the table above).

Spaces Stuck at "Starting"

Cause 1: With server_name="127.0.0.1", the HF Spaces proxy cannot reach the port, causing health checks to fail.

Cause 2: In gradio 5.x SSR (Server-Side Rendering) mode, the Node.js server startup can conflict with Spaces health checks.

Fix:

demo.launch(
    show_error=True,
    server_name="0.0.0.0",   # Changed from 127.0.0.1
    server_port=7860,
    ssr_mode=False,           # Disable SSR
)

Screenshots

Inference Results (Input, JSON, Detected Images)

Full inference result view

JSON Output and Detected Image Close-up

gr.JSON component and detected result images


Summary

ProblemFix
Model (700 MB) stored in the Spaces repoMoved to a separate HF Hub model repository
YOLOv5 source managed as a local copyUsed torch.hub GitHub caching instead
Slow startup due to model loading at bootChanged to lazy-load on first inference
PyTorch 2.6 weights_only breaking changeMonkey-patched torch.load
gradio_client bool schema bugMonkey-patched _json_schema_to_python_type
Generator passed to im.resize()Wrapped with tuple()
audioop removed in Python 3.13, breaking gradio 4.xUpgraded to gradio 5.9.1; changed to flagging_mode
Dependency error on ultralytics/yolov5 masterPinned to v7.0
Missing yolov5 v7.0 dependenciesAdded opencv-python-headless, ipython, matplotlib, seaborn, psutil, etc. to requirements.txt
Spaces stuck at "Starting"Changed to server_name="0.0.0.0", ssr_mode=False
JSON displayed as plain text (hard to read)Changed from gr.Textbox to gr.JSON component