matrice-inference 0.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of matrice-inference might be problematic. Click here for more details.

Files changed (37) hide show
  1. matrice_inference/__init__.py +72 -0
  2. matrice_inference/py.typed +0 -0
  3. matrice_inference/server/__init__.py +23 -0
  4. matrice_inference/server/inference_interface.py +176 -0
  5. matrice_inference/server/model/__init__.py +1 -0
  6. matrice_inference/server/model/model_manager.py +274 -0
  7. matrice_inference/server/model/model_manager_wrapper.py +550 -0
  8. matrice_inference/server/model/triton_model_manager.py +290 -0
  9. matrice_inference/server/model/triton_server.py +1248 -0
  10. matrice_inference/server/proxy_interface.py +371 -0
  11. matrice_inference/server/server.py +1004 -0
  12. matrice_inference/server/stream/__init__.py +0 -0
  13. matrice_inference/server/stream/app_deployment.py +228 -0
  14. matrice_inference/server/stream/consumer_worker.py +201 -0
  15. matrice_inference/server/stream/frame_cache.py +127 -0
  16. matrice_inference/server/stream/inference_worker.py +163 -0
  17. matrice_inference/server/stream/post_processing_worker.py +230 -0
  18. matrice_inference/server/stream/producer_worker.py +147 -0
  19. matrice_inference/server/stream/stream_pipeline.py +451 -0
  20. matrice_inference/server/stream/utils.py +23 -0
  21. matrice_inference/tmp/abstract_model_manager.py +58 -0
  22. matrice_inference/tmp/aggregator/__init__.py +18 -0
  23. matrice_inference/tmp/aggregator/aggregator.py +330 -0
  24. matrice_inference/tmp/aggregator/analytics.py +906 -0
  25. matrice_inference/tmp/aggregator/ingestor.py +438 -0
  26. matrice_inference/tmp/aggregator/latency.py +597 -0
  27. matrice_inference/tmp/aggregator/pipeline.py +968 -0
  28. matrice_inference/tmp/aggregator/publisher.py +431 -0
  29. matrice_inference/tmp/aggregator/synchronizer.py +594 -0
  30. matrice_inference/tmp/batch_manager.py +239 -0
  31. matrice_inference/tmp/overall_inference_testing.py +338 -0
  32. matrice_inference/tmp/triton_utils.py +638 -0
  33. matrice_inference-0.1.2.dist-info/METADATA +28 -0
  34. matrice_inference-0.1.2.dist-info/RECORD +37 -0
  35. matrice_inference-0.1.2.dist-info/WHEEL +5 -0
  36. matrice_inference-0.1.2.dist-info/licenses/LICENSE.txt +21 -0
  37. matrice_inference-0.1.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,330 @@
1
+ import logging
2
+ import threading
3
+ import time
4
+ import copy
5
+ from typing import Dict, Optional, Any, Set, Tuple
6
+ from queue import Queue, Empty
7
+ from collections import defaultdict, deque
8
+ import copy
9
+
10
+
11
+ class ResultsAggregator:
12
+ """
13
+ Optimized aggregation and combination of synchronized results from multiple deployments.
14
+ This component takes synchronized results and combines them into meaningful aggregated outputs
15
+ while maintaining consistent structure with individual deployment results.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ synchronized_results_queue: Queue,
21
+ aggregate_by_location: bool = False,
22
+ ):
23
+ """
24
+ Initialize the results aggregator.
25
+
26
+ Args:
27
+ synchronized_results_queue: Queue containing synchronized results from synchronizer
28
+ aggregate_by_location: Whether to aggregate by location
29
+ """
30
+ self.synchronized_results_queue = synchronized_results_queue
31
+ self.aggregated_results_queue = Queue()
32
+ self.aggregate_by_location = aggregate_by_location
33
+
34
+ # Threading and state management
35
+ self._stop_aggregation = threading.Event()
36
+ self._aggregation_thread: Optional[threading.Thread] = None
37
+ self._is_running = False
38
+ self._stats_lock = threading.Lock()
39
+
40
+ # Use more efficient data structure for tracking sent keys
41
+ # Use a deque with fixed maxlen for automatic cleanup
42
+ self._sent_keys: deque = deque(maxlen=50000) # More memory efficient than manual cleanup
43
+ self._sent_keys_set: Set[Tuple[str, str, int]] = set() # For O(1) lookups
44
+
45
+ # Statistics
46
+ self.stats = {
47
+ "start_time": None,
48
+ "results_processed": 0,
49
+ "aggregations_created": 0,
50
+ "errors": 0,
51
+ "last_error": None,
52
+ "last_error_time": None,
53
+ "duplicates_skipped": 0,
54
+ }
55
+
56
+ def start_aggregation(self) -> bool:
57
+ """
58
+ Start the results aggregation process.
59
+
60
+ Returns:
61
+ bool: True if aggregation started successfully, False otherwise
62
+ """
63
+ if self._is_running:
64
+ logging.warning("Results aggregation is already running")
65
+ return True
66
+
67
+ try:
68
+ self._is_running = True
69
+ self.stats["start_time"] = time.time()
70
+ self._stop_aggregation.clear()
71
+
72
+ # Start aggregation thread
73
+ self._aggregation_thread = threading.Thread(
74
+ target=self._aggregation_worker,
75
+ name="ResultsAggregator",
76
+ daemon=True,
77
+ )
78
+ self._aggregation_thread.start()
79
+
80
+ logging.info("Results aggregation started successfully")
81
+ return True
82
+
83
+ except Exception as exc:
84
+ self._record_error(f"Failed to start results aggregation: {str(exc)}")
85
+ self.stop_aggregation()
86
+ return False
87
+
88
+ def stop_aggregation(self):
89
+ """Stop the results aggregation process."""
90
+ if not self._is_running:
91
+ logging.info("Results aggregation is not running")
92
+ return
93
+
94
+ self._is_running = False
95
+ self._stop_aggregation.set()
96
+
97
+ logging.info("Stopping results aggregation...")
98
+
99
+ # Wait for aggregation thread to complete
100
+ if self._aggregation_thread and self._aggregation_thread.is_alive():
101
+ try:
102
+ self._aggregation_thread.join(timeout=5.0)
103
+ if self._aggregation_thread.is_alive():
104
+ logging.warning("Results aggregation thread did not stop gracefully")
105
+ except Exception as exc:
106
+ logging.error(f"Error joining aggregation thread: {exc}")
107
+
108
+ self._aggregation_thread = None
109
+ logging.info("Results aggregation stopped")
110
+
111
+ def _aggregation_worker(self):
112
+ """Optimized main aggregation worker thread for immediate processing."""
113
+ logging.info("Results aggregation worker started")
114
+ last_log_time = time.time()
115
+ log_interval = 30.0 # Log every 30 seconds
116
+
117
+ while not self._stop_aggregation.is_set():
118
+ try:
119
+ # Get synchronized result from queue
120
+ try:
121
+ synced_result = self.synchronized_results_queue.get(timeout=1.0)
122
+ except Empty:
123
+ continue
124
+
125
+ # Process the single synchronized result immediately
126
+ aggregated_result = self._aggregate_single_result(synced_result)
127
+
128
+ if aggregated_result:
129
+ # Add to output queue immediately
130
+ self.aggregated_results_queue.put(aggregated_result)
131
+
132
+ # Update statistics
133
+ with self._stats_lock:
134
+ self.stats["results_processed"] += 1
135
+ self.stats["aggregations_created"] += 1
136
+ else:
137
+ # Track duplicates
138
+ with self._stats_lock:
139
+ self.stats["duplicates_skipped"] += 1
140
+
141
+ # Mark task as done
142
+ self.synchronized_results_queue.task_done()
143
+
144
+ # Reduced frequency logging
145
+ current_time = time.time()
146
+ if (current_time - last_log_time) > log_interval:
147
+ with self._stats_lock:
148
+ processed = self.stats["results_processed"]
149
+ duplicates = self.stats["duplicates_skipped"]
150
+ queue_size = self.aggregated_results_queue.qsize()
151
+ if processed > 0 or duplicates > 0:
152
+ logging.debug(f"Aggregator: processed={processed}, duplicates={duplicates}, queue_size={queue_size}")
153
+ last_log_time = current_time
154
+
155
+ except Exception as exc:
156
+ if not self._stop_aggregation.is_set():
157
+ self._record_error(f"Error in aggregation worker: {str(exc)}")
158
+ time.sleep(0.1) # Prevent tight error loops
159
+
160
+ logging.info("Results aggregation worker stopped")
161
+
162
+ def _aggregate_single_result(self, sync_result: Dict) -> Optional[Dict]:
163
+ """Optimized aggregation of a single synchronized result."""
164
+ try:
165
+ # Extract deployment results
166
+ deployment_results = sync_result.get("deployment_results", {})
167
+ if not deployment_results:
168
+ return None
169
+
170
+ # Get stream info from synchronized result
171
+ stream_key = sync_result.get("stream_key")
172
+ input_order = sync_result.get("input_order")
173
+ stream_group_key = sync_result.get("stream_group_key")
174
+
175
+ if not stream_key or input_order is None:
176
+ return None
177
+
178
+ # Efficient duplicate checking using O(1) set lookup
179
+ key = (stream_group_key, stream_key, input_order)
180
+ if key in self._sent_keys_set:
181
+ return None # Duplicate, skip silently for performance
182
+
183
+ # Add to both deque (for automatic cleanup) and set (for fast lookup)
184
+ self._sent_keys.append(key)
185
+ self._sent_keys_set.add(key)
186
+
187
+ # Clean up set when deque automatically removes old items
188
+ if len(self._sent_keys_set) > self._sent_keys.maxlen:
189
+ # Only keep recent items in set - rebuild from deque
190
+ self._sent_keys_set = set(self._sent_keys)
191
+
192
+ # Extract input_stream and camera_info efficiently (avoid deep copy)
193
+ first_deployment_result = next(iter(deployment_results.values()))
194
+ first_app_result = first_deployment_result.get("result", {})
195
+ input_streams = first_app_result.get("input_streams", [])
196
+
197
+ # Get input stream data efficiently
198
+ if input_streams:
199
+ input_data = input_streams[0]
200
+ input_stream = copy.deepcopy(input_data.get("input_stream", input_data))
201
+ else:
202
+ input_stream = {}
203
+
204
+ # Get camera_info without deep copy
205
+ camera_info = first_app_result.get("camera_info", {})
206
+
207
+ # Collect all app results efficiently
208
+ agg_apps = []
209
+ current_timestamp = time.time()
210
+
211
+ for deployment_id, deployment_result in deployment_results.items():
212
+ app_result = deployment_result.get("result", {})
213
+ if not app_result:
214
+ continue
215
+
216
+ # Create optimized app result
217
+ optimized_app_result = {
218
+ **app_result, # Shallow copy for performance
219
+ "deployment_id": deployment_id,
220
+ "deployment_timestamp": deployment_result.get("timestamp", current_timestamp)
221
+ }
222
+
223
+ # Remove large content fields to save memory
224
+ if "input_streams" in optimized_app_result:
225
+ input_streams_clean = []
226
+ for item in optimized_app_result["input_streams"]:
227
+ clean_item = {k: v for k, v in item.items() if k != "content"}
228
+ if "input_stream" in clean_item and isinstance(clean_item["input_stream"], dict):
229
+ clean_item["input_stream"] = {k: v for k, v in clean_item["input_stream"].items() if k != "content"}
230
+ input_streams_clean.append(clean_item)
231
+ optimized_app_result["input_streams"] = input_streams_clean
232
+
233
+ agg_apps.append(optimized_app_result)
234
+
235
+ # Create optimized camera_results structure
236
+ camera_results = {
237
+ "input_stream": input_stream,
238
+ "camera_info": camera_info,
239
+ "agg_apps": agg_apps,
240
+ "aggregation_metadata": {
241
+ "stream_key": stream_key,
242
+ "input_order": input_order,
243
+ "stream_group_key": stream_group_key,
244
+ "deployment_count": len(deployment_results),
245
+ "aggregation_timestamp": current_timestamp,
246
+ "aggregation_type": "camera_results",
247
+ "synchronization_metadata": sync_result.get("synchronization_metadata", {})
248
+ }
249
+ }
250
+
251
+ return camera_results
252
+
253
+ except Exception as exc:
254
+ self._record_error(f"Error aggregating single result: {str(exc)}")
255
+ return None
256
+
257
+ def _record_error(self, error_message: str):
258
+ """Record an error in statistics."""
259
+ with self._stats_lock:
260
+ self.stats["errors"] += 1
261
+ self.stats["last_error"] = error_message
262
+ self.stats["last_error_time"] = time.time()
263
+ # Reduce logging frequency for performance
264
+ if self.stats["errors"] % 10 == 1: # Log every 10th error
265
+ logging.error(f"Aggregator error (#{self.stats['errors']}): {error_message}")
266
+
267
+ def get_stats(self) -> Dict[str, Any]:
268
+ """Get current aggregation statistics."""
269
+ with self._stats_lock:
270
+ stats = self.stats.copy()
271
+
272
+ # Add runtime statistics
273
+ if stats["start_time"]:
274
+ stats["runtime_seconds"] = time.time() - stats["start_time"]
275
+
276
+ stats["output_queue_size"] = self.aggregated_results_queue.qsize()
277
+ stats["sent_keys_count"] = len(self._sent_keys)
278
+ stats["sent_keys_set_count"] = len(self._sent_keys_set)
279
+
280
+ return stats
281
+
282
+ def get_health_status(self) -> Dict[str, Any]:
283
+ """Get health status of the aggregator."""
284
+ health = {
285
+ "status": "healthy",
286
+ "is_running": self._is_running,
287
+ "output_queue_size": self.aggregated_results_queue.qsize(),
288
+ "errors": self.stats["errors"],
289
+ }
290
+
291
+ # Check for recent errors (within last 60 seconds)
292
+ if (
293
+ self.stats["last_error_time"]
294
+ and (time.time() - self.stats["last_error_time"]) < 60
295
+ ):
296
+ health["status"] = "degraded"
297
+ health["reason"] = f"Recent error: {self.stats['last_error']}"
298
+ logging.warning(f"Aggregator degraded due to recent error: {self.stats['last_error']}")
299
+
300
+ # Check if output queue is getting full
301
+ queue_size = self.aggregated_results_queue.qsize()
302
+ if queue_size > 1000:
303
+ health["status"] = "degraded"
304
+ health["reason"] = f"Output queue too large ({queue_size} items)"
305
+ logging.warning(f"Aggregator degraded: output queue has {queue_size} items (threshold: 100)")
306
+
307
+ # Check if not running when it should be
308
+ if not self._is_running:
309
+ health["status"] = "unhealthy"
310
+ health["reason"] = "Aggregator is not running"
311
+ logging.error("Aggregator is not running")
312
+
313
+ return health
314
+
315
+ def cleanup(self):
316
+ """Clean up resources."""
317
+ self.stop_aggregation()
318
+
319
+ # Clear queues
320
+ try:
321
+ while not self.aggregated_results_queue.empty():
322
+ self.aggregated_results_queue.get_nowait()
323
+ except Exception:
324
+ pass
325
+
326
+ # Clear tracking data
327
+ self._sent_keys.clear()
328
+ self._sent_keys_set.clear()
329
+
330
+ logging.info("Results aggregator cleanup completed")