File size: 4,340 Bytes
b379113
 
 
 
 
8cfe717
b379113
8cfe717
 
 
 
 
 
 
 
 
 
 
 
 
 
d87e594
87e0c95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f2aeec5
 
 
 
 
 
 
 
 
87e0c95
 
 
 
b379113
 
 
 
 
 
 
d87e594
87e0c95
 
 
 
 
 
 
 
d87e594
 
 
b379113
 
 
 
 
 
 
8cfe717
 
 
b379113
 
 
 
 
f2aeec5
8cfe717
f2aeec5
e47b125
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
"""HuggingFace Spaces entry point for the Gradio demo (docs/04_ROADMAP.md, Phase 9).

Not used for local development -- `scripts/demo.py` is the local launcher,
with a required `--checkpoint` flag pointing anywhere on disk. A Space has no
such flag: HF Spaces runs `python app.py` with no arguments, so this file
fetches the checkpoints from the separate model repo
(huggingface.co/adarshcod30/drdetect-dr-screening -- research-use-only, see
MODEL_CARD.md there) via `hf_hub_download` instead of bundling them into this
Space's own repo. `hf_hub_download` caches each file locally after its first
fetch, so a Space restart does not re-download them.

Serves the 5-fold CNN ensemble (`ensemble_fold0.ckpt`..`ensemble_fold4.ckpt`),
not the single `best.ckpt` this Space originally shipped -- the ensemble
beat the single checkpoint on the DDR external test set's referable AUC
(0.899 vs 0.891, DeLong p=0.038, see project memory from the GPU
experimentation phase), so it is the better model to actually put in front
of a visitor. `best.ckpt` and its ONNX export stay published as the
documented, individually-benchmarked release artifact (MODEL_CARD.md); the
ensemble is 5 of that same training recipe's cross-validation folds, run
together, which is why five ~46MB files replace the one ~46MB file below
rather than a differently-sized model.

This project's model is CPU-only by design (see
`drdetect.serve.pipeline.load_grader`'s own docstring: "a district screening
kiosk is not assumed to have [a GPU]"), but the Space is hosted on HF's free
ZeroGPU hardware tier (free accounts can no longer create free CPU-basic
Gradio Spaces). Two ZeroGPU-specific fixes were needed, found by reading this
Space's own runtime error, not guessed:

1. `import spaces` early -- without it, HF's supervisor stops the app
   shortly after a clean startup ("Stopping Node.js server..." with no other
   error visible in the application log).
2. **`_zerogpu_registration` below**: even with (1), the platform's own
   `get_space_runtime` reported the real reason plainly --
   `"No @spaces.GPU function detected during startup"` -- ZeroGPU spaces
   require at least one such function to exist, whether or not it is ever
   called. This function is never invoked by the demo; grading always runs
   on CPU exactly as the rest of this project does. It exists solely to
   satisfy that platform check without changing this project's actual
   CPU-only inference path.
3. **`device="cpu"` passed explicitly to `build_interface` below**: its
   default auto-detection (`drdetect.serve.demo`) checks
   `torch.cuda.is_available()`, which ZeroGPU makes report `True` even
   outside an actual `@spaces.GPU` grant -- auto-detection then picked
   "cuda" and crashed the first real request with `RuntimeError: Low-level
   CUDA init... reached` the moment a tensor was moved to it, since nothing
   in the demo path is wrapped in `@spaces.GPU`. Forcing "cpu" here matches
   this project's actual design (CPU-only inference throughout) rather than
   trusting a detection heuristic that ZeroGPU's emulation invalidates.

Both are wrapped so this file still runs unmodified outside a Space
(`spaces` is not, and should not be, a dependency of the main `drdetect`
package).
"""

from __future__ import annotations

import sys
from pathlib import Path

try:
    import spaces

    @spaces.GPU
    def _zerogpu_registration() -> None:
        """Never called -- exists only so ZeroGPU's startup check finds a
        decorated function. Real inference always runs on CPU (see module
        docstring point 2)."""
        return None
except ImportError:
    pass

sys.path.insert(0, str(Path(__file__).resolve().parent / "src"))

MODEL_REPO = "adarshcod30/drdetect-dr-screening"

if __name__ == "__main__":
    from huggingface_hub import hf_hub_download

    checkpoints = [
        hf_hub_download(repo_id=MODEL_REPO, filename=f"ensemble_fold{i}.ckpt") for i in range(5)
    ]

    from drdetect.serve.demo import build_interface

    # regression loss, not ce: it won the locked external evaluation decisively
    # (docs/22_PHASE8_VALIDATION_RESULTS.md, referable AUC 0.924 vs 0.888, DeLong p=6.1e-10)
    demo = build_interface(
        checkpoints, backbone="efficientnet_b0", loss_name="regression", size=512, device="cpu"
    )
    demo.launch(ssr_mode=False)