TerryStewart commited on
Commit
4552ca6
·
verified ·
1 Parent(s): 8f08262

Delete my_chute.py

Browse files
Files changed (1) hide show
  1. my_chute.py +0 -442
my_chute.py DELETED
@@ -1,442 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import sys
4
- from importlib.machinery import PathFinder
5
- from importlib.util import module_from_spec, spec_from_file_location
6
- from inspect import signature
7
- from pathlib import Path
8
- from site import getsitepackages, getusersitepackages
9
- from sysconfig import get_paths
10
- from tempfile import NamedTemporaryFile
11
- from typing import Any, Generator
12
- from urllib.parse import quote
13
-
14
- import torch
15
- from aiohttp import ClientSession
16
- from chutes.chute import Chute, NodeSelector
17
- from chutes.image import Image
18
- from base64 import b64decode
19
- from cv2 import CAP_PROP_FRAME_COUNT, VideoCapture, imdecode, IMREAD_COLOR
20
- from huggingface_hub import snapshot_download
21
- from numpy import ndarray, frombuffer, uint8
22
- from pydantic import BaseModel
23
- from yaml import safe_load
24
-
25
- ##============VARIABLES=======================
26
- HF_REPO_NAME = "TerryStewart/beverage-detection"
27
- HF_REPO_REVISION = "main"
28
- CHUTES_USERNAME = "kevinStewart"
29
- CHUTE_NAME = "beverage-detection"
30
- FILENAME = "miner.py"
31
- CLASSNAME = "Miner"
32
- CHUTE_CONFIG_FILENAME = "chute_config.yml"
33
- MAX_LOADED_MODEL_SIZE_GB = 5.0
34
-
35
-
36
- ##=========DATA CLASSES================
37
- class TVFrame(BaseModel):
38
- frame_id: int
39
- url: str | None = None
40
- data: str | None = None
41
-
42
-
43
- class TVPredictInput(BaseModel):
44
- url: str | None = None
45
- frames: list[TVFrame] | None = None
46
- meta: dict[str, Any] = {}
47
-
48
-
49
- class TVPredictOutput(BaseModel):
50
- success: bool
51
- predictions: dict[str, list[dict]] | None = None
52
- error: str | None = None
53
-
54
-
55
- ##=========UTILITY FUNCTIONS=================
56
- def get_whitelisted_paths() -> set[Path]:
57
- STDLIB_ROOT = Path(get_paths()["stdlib"]).resolve()
58
- SITE_PACKAGES_ROOTS = {Path(p).resolve() for p in getsitepackages()}
59
- SITE_PACKAGES_ROOTS.add(Path(getusersitepackages()).resolve())
60
-
61
- return {STDLIB_ROOT, *SITE_PACKAGES_ROOTS}
62
-
63
-
64
- def post_check_files_against_whitelist(*, before: set[str], miner_path: Path) -> None:
65
- miner_path = miner_path.resolve()
66
- WHITELIST = get_whitelisted_paths()
67
- after = set(sys.modules.keys()) - before
68
- offenders = []
69
- for module_loaded_name in sorted(after):
70
- module_loaded = sys.modules.get(module_loaded_name)
71
- if module_loaded is None:
72
- continue
73
- module_loaded_file = getattr(module_loaded, "__file__", None)
74
- if not module_loaded_file:
75
- continue
76
- module_loaded_filepath = Path(module_loaded_file).resolve()
77
- if module_loaded_filepath == miner_path:
78
- continue
79
- if any(module_loaded_filepath.is_relative_to(root) for root in WHITELIST):
80
- continue
81
- offenders.append(module_loaded_name)
82
-
83
- if offenders:
84
- raise ImportError(
85
- f"Miner loaded modules outside the whitelist: {', '.join(offenders)}"
86
- )
87
-
88
-
89
- def verify_cuda_version() -> str:
90
- min_version = (12, 8)
91
- if not torch.cuda.is_available():
92
- return "❌ CUDA is not available. Make sure you have a CUDA-capable GPU and correct drivers installed"
93
- device_count = torch.cuda.device_count()
94
- if device_count < 1:
95
- return "❌ No CUDA devices detected"
96
- cuda_version_str = torch.version.cuda
97
- if cuda_version_str is None:
98
- return "❌ PyTorch was not compiled with CUDA support"
99
- try:
100
- cuda_version = tuple(map(int, cuda_version_str.split(".")))
101
- except Exception as e:
102
- return f"❌ Unable to parse CUDA version string: {e}"
103
- if cuda_version < min_version:
104
- return f"❌ CUDA version {cuda_version_str} is too old. Requires >= {min_version[0]}.{min_version[1]}"
105
- return f"CUDA {cuda_version_str} detected_with {device_count} device(s)"
106
-
107
-
108
- def load_chute_config_from_hf_repo(config_path: Path) -> dict:
109
- try:
110
- if not config_path.exists():
111
- raise ValueError("No config file found in repo")
112
-
113
- with config_path.open() as configuration_file:
114
- config = safe_load(configuration_file)
115
- print("✅ Loaded Chutes Configuration")
116
- return config or {}
117
- except Exception as e:
118
- print(f"⚠️ Failed to load Chutes Configuration. Using defaults: {e}")
119
- return {}
120
-
121
-
122
- def safe_instantiate(cls, config: dict):
123
- params = {k: v for k, v in config.items() if k in signature(cls).parameters}
124
- print(
125
- f"The following parameters will be applied when instantiating {cls.__name__}: {params}"
126
- )
127
- obj = cls(**params)
128
- for function_name, value in config.items():
129
- if not hasattr(obj, function_name) or not callable(getattr(obj, function_name)):
130
- continue
131
- print(f"applying .{function_name}({value})")
132
- method = getattr(obj, function_name)
133
- if isinstance(value, list):
134
- for v in value:
135
- if isinstance(v, (tuple, list)):
136
- method(*v)
137
- else:
138
- method(v)
139
- else:
140
- method(value)
141
- return obj
142
-
143
-
144
- def load_chute(
145
- hf_repo: str,
146
- hf_revision: str,
147
- config_filename: str,
148
- chutes_username: str,
149
- chute_name: str,
150
- ) -> Chute:
151
- hf_repo_path = Path(snapshot_download(hf_repo, revision=hf_revision))
152
- print("✅ Huggingface Hub repo downloaded")
153
-
154
- config = load_chute_config_from_hf_repo(config_path=hf_repo_path / config_filename)
155
- print("✅ Config file loaded")
156
-
157
- image_config = config.get("Image", {})
158
- image_config.update(
159
- {
160
- "username": chutes_username,
161
- "name": chute_name,
162
- "tag":"latest",
163
- "readme": "README.md",
164
- }
165
- )
166
- chute_image = safe_instantiate(cls=Image, config=image_config)
167
- print("✅ Image instantiated")
168
-
169
- machine_specs = safe_instantiate(
170
- cls=NodeSelector, config=config.get("NodeSelector", {})
171
- )
172
- print("✅ NodeSelector instantiated")
173
-
174
- chute_config = config.get("Chute", {})
175
- chute_config.update(
176
- {
177
- "username": chutes_username,
178
- "name": chute_name,
179
- "image": chute_image,
180
- "node_selector": machine_specs,
181
- "allow_external_egress":True,
182
- "readme": "README.md",
183
- }
184
- )
185
- chute = safe_instantiate(cls=Chute, config=chute_config)
186
- print("✅ Chute instantiated")
187
- return chute
188
-
189
-
190
- def load_miner_from_hf_repo(path_hf_repo: Path, filename: str, classname: str):
191
- path_hf_repo = path_hf_repo.resolve()
192
- path_miner = path_hf_repo / filename
193
- if not path_miner.is_file():
194
- raise FileNotFoundError(
195
- f"❌ Required file '{filename}' not in repo {path_hf_repo}"
196
- )
197
-
198
- WHITELIST = get_whitelisted_paths()
199
-
200
- class HFRepoImportBlocker(PathFinder):
201
-
202
- @classmethod
203
- def find_spec(cls, fullname, path=None, target=None):
204
- spec = super().find_spec(fullname, path, target)
205
- if spec is None or spec.origin is None:
206
- return spec
207
- origin_path = Path(spec.origin).resolve()
208
- if any(origin_path.is_relative_to(root) for root in WHITELIST):
209
- return spec
210
- raise ImportError(
211
- f"❌ Import blocked: '{fullname}' (file: {origin_path}) is not in the whitelist. All code related to the miner MUST be inside {filename}"
212
- )
213
-
214
- try:
215
- before_exec = set(sys.modules.keys())
216
- sys.meta_path.insert(0, HFRepoImportBlocker)
217
- spec = spec_from_file_location("miner", path_miner)
218
- module = module_from_spec(spec)
219
- sys.modules["miner"] = module
220
- spec.loader.exec_module(module)
221
- miner_object = getattr(module, classname)
222
- miner = miner_object(path_hf_repo)
223
- post_check_files_against_whitelist(before=before_exec, miner_path=path_miner)
224
- except Exception as e:
225
- raise e
226
- finally:
227
- sys.meta_path.remove(HFRepoImportBlocker)
228
- return miner
229
-
230
-
231
- def object_size_in_bytes(current_obj: Any, seen_ids: set[int], limit: int) -> int:
232
- obj_id = id(current_obj)
233
- if obj_id in seen_ids or limit <= 0:
234
- return 0
235
- seen_ids.add(obj_id)
236
- total_size = 0
237
- if isinstance(current_obj, torch.Tensor):
238
- return current_obj.numel() * current_obj.element_size()
239
- if isinstance(current_obj, ndarray):
240
- return current_obj.nbytes
241
- try:
242
- total_size += sys.getsizeof(current_obj)
243
- except TypeError:
244
- pass
245
- if isinstance(current_obj, dict):
246
- for key, value in current_obj.items():
247
- total_size += object_size_in_bytes(key, seen_ids, limit - 1)
248
- total_size += object_size_in_bytes(value, seen_ids, limit - 1)
249
- elif isinstance(current_obj, (list, tuple, set)):
250
- try:
251
- for item in current_obj:
252
- total_size += object_size_in_bytes(item, seen_ids, limit - 1)
253
- except TypeError:
254
- pass
255
- elif hasattr(current_obj, "__dict__"):
256
- for value in current_obj.__dict__.values():
257
- total_size += object_size_in_bytes(value, seen_ids, limit - 1)
258
- return total_size
259
-
260
-
261
- def get_miner_size_gb(miner) -> float:
262
- total_size_bytes = object_size_in_bytes(
263
- current_obj=miner, seen_ids=set(), limit=100
264
- )
265
- return total_size_bytes / (1024 ** 3)
266
-
267
-
268
- def get_video_frames_in_batches(
269
- video: VideoCapture, batch_size: int
270
- ) -> Generator[list[ndarray], None, None]:
271
- batch = []
272
- while True:
273
- ok, frame = video.read()
274
- if not ok:
275
- if batch:
276
- yield batch
277
- break
278
-
279
- batch.append(frame)
280
-
281
- if len(batch) >= batch_size:
282
- yield batch
283
- batch = []
284
-
285
-
286
- ##=========ENDPOINTS================
287
- chute = load_chute(
288
- hf_repo=HF_REPO_NAME,
289
- hf_revision=HF_REPO_REVISION,
290
- chutes_username=CHUTES_USERNAME,
291
- chute_name=CHUTE_NAME,
292
- config_filename=CHUTE_CONFIG_FILENAME,
293
- )
294
-
295
-
296
- @chute.on_startup()
297
- async def load_model(self) -> None:
298
- try:
299
- self.cuda_status = verify_cuda_version()
300
- except Exception as e:
301
- self.cuda_status = f"❌ Failed to check cuda status: {e}"
302
- print(self.cuda_status)
303
-
304
- self.miner_size = None
305
- try:
306
- hf_repo_path = Path(snapshot_download(HF_REPO_NAME, revision=HF_REPO_REVISION))
307
- print("✅ Huggingface Hub repo downloaded")
308
-
309
- self.miner = load_miner_from_hf_repo(
310
- path_hf_repo=hf_repo_path, filename=FILENAME, classname=CLASSNAME
311
- )
312
- print(f"✅ Miner loaded {self.miner}")
313
- self.miner_size = get_miner_size_gb(self.miner)
314
- if self.miner_size > MAX_LOADED_MODEL_SIZE_GB:
315
- raise ValueError(
316
- f"Loaded Miner memory footprint {self.miner_size:.2f} GB exceeds the allowed limit of {MAX_LOADED_MODEL_SIZE_GB:.2f} GB. "
317
- )
318
- self.status = "healthy"
319
- except Exception as e:
320
- self.status = f"❌ Failed to load miner: {e}"
321
- self.miner = None
322
- print(self.status)
323
-
324
-
325
- @chute.cord(public_api_path="/health")
326
- async def health(self, *args, **kwargs) -> dict[str, Any]:
327
- return {
328
- "status": self.status,
329
- "model_loaded": str(self.miner),
330
- "memory_gb": self.miner_size,
331
- "cuda": self.cuda_status,
332
- }
333
-
334
-
335
- @chute.cord(
336
- public_api_path="/predict",
337
- )
338
- async def predict(self, data: TVPredictInput) -> dict:
339
- try:
340
- if self.miner is None:
341
- raise ValueError("Models were not properly loaded!")
342
-
343
- metadata = data.meta
344
- batch_size = metadata.get("batch_size", 64)
345
- n_keypoints = metadata.get("n_keypoints", 32)
346
-
347
- frame_results = []
348
-
349
- if data.frames:
350
- frame_entries = [(f.frame_id, f.url, f.data) for f in data.frames]
351
- async with ClientSession() as session:
352
- loaded_frames: list[tuple[int, ndarray]] = []
353
- for frame_id, frame_url, frame_data in frame_entries:
354
- if frame_data:
355
- try:
356
- content = b64decode(frame_data)
357
- except Exception as e:
358
- raise ValueError(f"Failed to decode frame {frame_id}: {e}")
359
- elif frame_url:
360
- url = f"https://proxy.chutes.ai/misc/proxy?url={quote(frame_url, safe='')}"
361
- async with session.get(url) as response:
362
- response.raise_for_status()
363
- content = await response.read()
364
- else:
365
- raise ValueError(f"Frame {frame_id} missing url or data")
366
-
367
- arr = frombuffer(content, dtype=uint8)
368
- img = imdecode(arr, IMREAD_COLOR)
369
- if img is None:
370
- raise ValueError(f"Failed to decode frame {frame_id}")
371
- loaded_frames.append((frame_id, img))
372
-
373
- for i in range(0, len(loaded_frames), batch_size):
374
- batch = loaded_frames[i: i + batch_size]
375
- batch_images = [img for _, img in batch]
376
- batch_frame_ids = [fid for fid, _ in batch]
377
- print(f"Predicting Batch: {i // batch_size + 1}")
378
- batch_frame_results = self.miner.predict_batch(
379
- batch_images=batch_images,
380
- offset=0,
381
- n_keypoints=n_keypoints,
382
- )
383
- for frame_id, frame_result in zip(
384
- batch_frame_ids, batch_frame_results, strict=True
385
- ):
386
- fr = frame_result.model_dump()
387
- fr["frame_id"] = frame_id
388
- frame_results.append(fr)
389
- else:
390
- if not data.url:
391
- raise ValueError("Missing both video url and frames payload")
392
-
393
- url = f"https://proxy.chutes.ai/misc/proxy?url={quote(data.url, safe='')}"
394
- async with ClientSession() as session:
395
- async with session.get(url) as response:
396
- response.raise_for_status()
397
- content = await response.read()
398
- print("✅ Challenge video downloaded. Saving video to temporary file.")
399
-
400
- with NamedTemporaryFile(prefix="sv_video_", suffix=".mp4") as f:
401
- f.write(content)
402
- cap = VideoCapture(f.name)
403
- if not cap.isOpened():
404
- raise ValueError(
405
- f"Problem accessing downloaded video {f.name}"
406
- )
407
-
408
- n_frames = int(cap.get(CAP_PROP_FRAME_COUNT))
409
- print(
410
- f"Processing video with {n_frames} frames in batches of {batch_size}"
411
- )
412
-
413
- for batch_number, images in enumerate(
414
- get_video_frames_in_batches(
415
- video=cap, batch_size=batch_size
416
- )
417
- ):
418
- frame_number = batch_size * batch_number
419
- print(f"Predicting Batch: {batch_number + 1}")
420
- batch_frame_results = self.miner.predict_batch(
421
- batch_images=images,
422
- offset=frame_number,
423
- n_keypoints=n_keypoints,
424
- )
425
- frame_results.extend(
426
- [
427
- frame_result.model_dump()
428
- for frame_result in batch_frame_results
429
- ]
430
- )
431
-
432
- cap.release()
433
- print("✅ Frame Predictions Completed")
434
-
435
- result = TVPredictOutput(success=True, predictions={"frames": frame_results})
436
-
437
- except Exception as e:
438
- result = TVPredictOutput(
439
- success=False,
440
- error=f"❌ There was a problem during prediction: {e}",
441
- )
442
- return result.model_dump(mode="json")