Skip to content

Commit caa4cb1

Browse files
authored
Sitecustomize based auto-instrumentation (#7)
1 parent 54f44d4 commit caa4cb1

24 files changed

Lines changed: 1484 additions & 212 deletions

examples/django_gemma/demo.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [[ -z "${WILDEDGE_DSN:-}" ]]; then
5+
echo 'Set WILDEDGE_DSN first, e.g. export WILDEDGE_DSN="https://<secret>@ingest.wildedge.dev/<key>"' >&2
6+
exit 1
7+
fi
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
cd "${SCRIPT_DIR}"
11+
12+
uv sync
13+
14+
uv run wildedge doctor --integrations gguf --hubs huggingface
15+
16+
# wildedge run replaces this process (os.execle) so sitecustomize.py
17+
# auto-installs the runtime before Django loads, patching Llama.__init__
18+
# for automatic inference tracking.
19+
#
20+
# Server choice:
21+
# macOS — waitress (thread-pool, no fork). Metal is initialised once in the
22+
# main process and shared safely across request threads.
23+
# Linux — gunicorn (multi-process fork). Requires llama-cpp-python built
24+
# without Metal: CMAKE_ARGS="-DGGML_METAL=OFF" pip install llama-cpp-python
25+
# Then: wildedge run ... -- gunicorn gemmaapp.wsgi:application --config gunicorn.conf.py
26+
if [[ "$(uname)" == "Darwin" ]]; then
27+
uv run wildedge run \
28+
--print-startup-report \
29+
--integrations gguf \
30+
--hubs huggingface \
31+
-- waitress-serve --port=8100 gemmaapp.wsgi:application
32+
else
33+
uv run wildedge run \
34+
--print-startup-report \
35+
--integrations gguf \
36+
--hubs huggingface \
37+
-- gunicorn gemmaapp.wsgi:application --config gunicorn.conf.py
38+
fi
39+
40+
# Test with:
41+
# curl -s -X POST http://localhost:8100/infer/ \
42+
# -H "Content-Type: application/json" \
43+
# -d '{"prompt": "What is on-device AI in one sentence?"}' | jq .

examples/django_gemma/gemmaapp/__init__.py

Whitespace-only changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
SECRET_KEY = "dev-only-not-for-production"
2+
DEBUG = True
3+
ALLOWED_HOSTS = ["*"]
4+
INSTALLED_APPS = ["gemmaapp"]
5+
ROOT_URLCONF = "gemmaapp.urls"
6+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from django.urls import path
2+
3+
from gemmaapp import views
4+
5+
urlpatterns = [
6+
path("infer/", views.infer),
7+
]
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"""Gemma inference view.
2+
3+
The Llama constructor is patched automatically by `wildedge run --integrations gguf`
4+
via sitecustomize.py — load/unload/inference events are tracked without any
5+
wildedge imports here.
6+
7+
On macOS, waitress (thread-pool, no fork) is used as the WSGI server.
8+
Metal is initialised once at startup in the main process and shared safely
9+
across request threads. gunicorn (fork-based) requires llama-cpp-python built
10+
without Metal on macOS (CMAKE_ARGS="-DGGML_METAL=OFF").
11+
"""
12+
13+
import json
14+
import os
15+
import threading
16+
17+
from django.http import JsonResponse
18+
from django.views.decorators.csrf import csrf_exempt
19+
from django.views.decorators.http import require_POST
20+
from llama_cpp import Llama
21+
22+
REPO = "bartowski/gemma-2-2b-it-GGUF"
23+
FILE = "gemma-2-2b-it-Q4_K_M.gguf"
24+
25+
_llm = Llama.from_pretrained(
26+
repo_id=REPO,
27+
filename=FILE,
28+
n_ctx=512,
29+
n_gpu_layers=int(os.environ.get("GPU_LAYERS", "-1")),
30+
verbose=False,
31+
)
32+
33+
# Llama inference is not thread-safe on a single context — serialise requests.
34+
_llm_lock = threading.Lock()
35+
36+
37+
@csrf_exempt
38+
@require_POST
39+
def infer(request):
40+
try:
41+
body = json.loads(request.body)
42+
except json.JSONDecodeError:
43+
return JsonResponse({"error": "invalid JSON"}, status=400)
44+
45+
prompt = body.get("prompt", "").strip()
46+
if not prompt:
47+
return JsonResponse({"error": "prompt is required"}, status=400)
48+
49+
with _llm_lock:
50+
result = _llm(prompt, max_tokens=256, temperature=0.7)
51+
52+
text = result["choices"][0]["text"].strip()
53+
return JsonResponse({"response": text})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import os
2+
3+
from django.core.wsgi import get_wsgi_application
4+
5+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gemmaapp.settings")
6+
7+
application = get_wsgi_application()
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""Gunicorn configuration — Linux only.
2+
3+
On macOS use waitress instead (demo.sh selects automatically).
4+
Requires llama-cpp-python built without Metal:
5+
CMAKE_ARGS="-DGGML_METAL=OFF" pip install llama-cpp-python --no-binary llama-cpp-python
6+
7+
With CPU-only GGML, the model loaded via preload_app=True in the master is
8+
inherited safely by forked workers via copy-on-write.
9+
"""
10+
11+
workers = 2
12+
bind = "0.0.0.0:8100"
13+
timeout = 120
14+
preload_app = True
15+
control_socket_disable = True

examples/django_gemma/manage.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env python
2+
import os
3+
import sys
4+
5+
if __name__ == "__main__":
6+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "gemmaapp.settings")
7+
from django.core.management import execute_from_command_line
8+
9+
execute_from_command_line(sys.argv)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[project]
2+
name = "wildedge-django-gemma"
3+
version = "0.1.0"
4+
requires-python = ">=3.10"
5+
dependencies = [
6+
"wildedge-sdk",
7+
"django",
8+
"gunicorn", # Linux (fork-based, CPU-only llama-cpp required on macOS)
9+
"waitress", # macOS (thread-based, no fork — works with Metal)
10+
"llama-cpp-python",
11+
"huggingface-hub",
12+
]
13+
14+
[tool.uv.sources]
15+
wildedge-sdk = { path = "../..", editable = true }

0 commit comments

Comments
 (0)