MacBook pro commited on
Commit
32226d2
·
1 Parent(s): 79dbfa1

docs: metrics endpoints & smoothing env vars; feat: OneEuro env initialization

Browse files
Files changed (3) hide show
  1. README.md +55 -0
  2. avatar_pipeline.py +16 -10
  3. liveportrait_engine.py +23 -18
README.md CHANGED
@@ -150,6 +150,11 @@ MIT License - Feel free to use and modify for your projects!
150
  ## Metrics Endpoints
151
  - `GET /metrics` – JSON with audio/video counters, EMAs (loop interval, inference), rolling FPS, frame interval EMA.
152
  - `GET /gpu` – GPU availability & memory (torch or `nvidia-smi` fallback).
 
 
 
 
 
153
 
154
  Example:
155
  ```bash
@@ -198,6 +203,56 @@ If the Space shows a perpetual "Restarting" badge:
198
  If problems persist, capture the Container log stack trace and open an issue.
199
 
200
  ## Enable ONNX Model Downloads (Safe LivePortrait)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
 
202
  To pull LivePortrait ONNX files into the container at runtime and enable the safe animation path:
203
 
 
150
  ## Metrics Endpoints
151
  - `GET /metrics` – JSON with audio/video counters, EMAs (loop interval, inference), rolling FPS, frame interval EMA.
152
  - `GET /gpu` – GPU availability & memory (torch or `nvidia-smi` fallback).
153
+ - `GET /metrics/async` – Async worker stats (frames submitted/processed, queue depth, last latency ms).
154
+ - `GET /metrics/stage_histogram` – Histogram buckets of recent inference stage latencies (snapshot window).
155
+ - `GET /metrics/motion` – Recent motion magnitudes (normalized) plus tail statistics.
156
+ - `GET /metrics/pacing` – Latency EMA and pacing hint multiplier ( >1.0 suggests you can raise FPS, <1.0 suggests throttling ).
157
+ - `POST /smoothing/update` – Runtime update of One Euro keypoint smoothing params. JSON body keys: `min_cutoff`, `beta`, `d_cutoff` (all optional floats).
158
 
159
  Example:
160
  ```bash
 
203
  If problems persist, capture the Container log stack trace and open an issue.
204
 
205
  ## Enable ONNX Model Downloads (Safe LivePortrait)
206
+ ## Advanced Real-time Metrics & Control
207
+
208
+ New runtime observability & control surfaces were added to tune real-time performance:
209
+
210
+ ### Endpoints Recap
211
+ See Metrics Endpoints section above. Typical usage examples:
212
+
213
+ ```bash
214
+ curl -s http://localhost:7860/metrics/async | jq
215
+ curl -s http://localhost:7860/metrics/pacing | jq '.latency_ema_ms, .pacing_hint'
216
+ curl -s http://localhost:7860/metrics/motion | jq '.recent_motion[-5:]'
217
+ ```
218
+
219
+ ### Pacing Hint Logic
220
+ `pacing_hint` is derived from a latency exponential moving average vs target frame time:
221
+ - ~1.0: Balanced.
222
+ - <0.85: System overloaded – consider lowering capture FPS or resolution.
223
+ - >1.15: Headroom available – you may increase FPS modestly.
224
+
225
+ ### Motion Magnitude
226
+ Aggregated from per-frame keypoint motion vectors; higher values trigger more frequent face detection to avoid drift. Low motion stretches automatically reduce detection frequency to save compute.
227
+
228
+ ### One Euro Smoothing Parameters
229
+ You can initialize or override smoothing parameters via environment variables:
230
+
231
+ | Variable | Default | Meaning |
232
+ |----------|---------|---------|
233
+ | `MIRAGE_ONEEURO_MIN_CUTOFF` | 1.0 | Base cutoff frequency controlling overall smoothing strength |
234
+ | `MIRAGE_ONEEURO_BETA` | 0.05 | Speed coefficient (higher reduces lag during fast motion) |
235
+ | `MIRAGE_ONEEURO_D_CUTOFF` | 1.0 | Derivative cutoff for velocity filtering |
236
+
237
+ Runtime adjustments:
238
+ ```bash
239
+ curl -X POST http://localhost:7860/smoothing/update \
240
+ -H 'Content-Type: application/json' \
241
+ -d '{"min_cutoff":0.8, "beta":0.07}'
242
+ ```
243
+ Missing keys leave existing values unchanged. The response echoes the active parameters.
244
+
245
+ ### Latency Histogram Snapshots
246
+ `/metrics/stage_histogram` exposes periodic snapshots (e.g. every N frames) of stage latency distribution to help identify tail regressions. Use to tune pacing thresholds or decide on model quantization.
247
+
248
+ ## Environment Variables Summary (New Additions)
249
+
250
+ | Name | Purpose | Default |
251
+ |------|---------|---------|
252
+ | `MIRAGE_ONEEURO_MIN_CUTOFF` | One Euro base cutoff | 1.0 |
253
+ | `MIRAGE_ONEEURO_BETA` | One Euro speed coefficient | 0.05 |
254
+ | `MIRAGE_ONEEURO_D_CUTOFF` | One Euro derivative cutoff | 1.0 |
255
+
256
 
257
  To pull LivePortrait ONNX files into the container at runtime and enable the safe animation path:
258
 
avatar_pipeline.py CHANGED
@@ -378,21 +378,27 @@ class RealTimeAvatarPipeline:
378
  self._frame_submit_count = 0
379
  self._frame_process_count = 0
380
  # Keypoint smoothing filter
381
- self._kp_filter = KeypointOneEuro(K=21, C=3, min_cutoff=1.0, beta=0.05, d_cutoff=1.0)
 
 
 
 
 
 
382
  self._prev_motion_raw = None
383
  # Adaptive detection interval tracking
384
  self._dynamic_detect_interval = self.config.detect_interval
385
  self._recent_motion_magnitudes = deque(maxlen=30)
386
  self._consecutive_detect_fail = 0
387
- # Extended motion history for metrics
388
- self._motion_history = deque(maxlen=300)
389
- # Latency histogram snapshots (long window)
390
- self._latency_history = deque(maxlen=500)
391
- self._latency_hist_snapshots = [] # list of {timestamp, buckets}
392
- # Frame pacing
393
- self._pacing_hint = 1.0 # multiplier suggestion (1.0 = normal)
394
- self._target_frame_time = 1.0 / max(self.config.target_fps, 1)
395
- self._latency_ema = None
396
 
397
  async def initialize(self):
398
  """Initialize all models"""
 
378
  self._frame_submit_count = 0
379
  self._frame_process_count = 0
380
  # Keypoint smoothing filter
381
+ try:
382
+ min_cut = float(os.getenv('MIRAGE_ONEEURO_MIN_CUTOFF', '1.0'))
383
+ beta = float(os.getenv('MIRAGE_ONEEURO_BETA', '0.05'))
384
+ d_cut = float(os.getenv('MIRAGE_ONEEURO_D_CUTOFF', '1.0'))
385
+ except Exception:
386
+ min_cut, beta, d_cut = 1.0, 0.05, 1.0
387
+ self._kp_filter = KeypointOneEuro(K=21, C=3, min_cutoff=min_cut, beta=beta, d_cutoff=d_cut)
388
  self._prev_motion_raw = None
389
  # Adaptive detection interval tracking
390
  self._dynamic_detect_interval = self.config.detect_interval
391
  self._recent_motion_magnitudes = deque(maxlen=30)
392
  self._consecutive_detect_fail = 0
393
+ # Extended motion history for metrics
394
+ self._motion_history = deque(maxlen=300)
395
+ # Latency histogram snapshots (long window)
396
+ self._latency_history = deque(maxlen=500)
397
+ self._latency_hist_snapshots = [] # list of {timestamp, buckets}
398
+ # Frame pacing
399
+ self._pacing_hint = 1.0 # multiplier suggestion (1.0 = normal)
400
+ self._target_frame_time = 1.0 / max(self.config.target_fps, 1)
401
+ self._latency_ema = None
402
 
403
  async def initialize(self):
404
  """Initialize all models"""
liveportrait_engine.py CHANGED
@@ -63,7 +63,8 @@ class LivePortraitONNX:
63
 
64
  # Performance tracking
65
  self.inference_times = []
66
- self._did_warmup = False
 
67
 
68
  def _get_onnx_providers(self) -> List[str]:
69
  """Get optimal ONNX execution providers. Enforce GPU if required."""
@@ -378,10 +379,30 @@ class LivePortraitONNX:
378
  return None
379
 
380
  def extract_motion_parameters(self, driving_image: np.ndarray) -> Optional[np.ndarray]:
381
- """Extract motion parameters from driving image"""
 
 
382
  if self.motion_session is None:
383
  logger.error("Motion model not loaded")
384
  return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
 
386
  def warmup(self) -> bool:
387
  """Run a minimal generator inference with zero motion to prime kernels and caches.
@@ -414,22 +435,6 @@ class LivePortraitONNX:
414
  except Exception as e:
415
  logger.debug(f"Warmup skipped: {e}")
416
  return False
417
-
418
- try:
419
- motion = self._run_motion_for_image(driving_image)
420
- # Prefer explicit kp_driving from motion model if available
421
- if isinstance(motion, dict):
422
- kp_drive = motion.get('kp_driving') or motion.get('driving')
423
- if kp_drive is None:
424
- # Fallback to first array value
425
- kp_drive = next((v for v in motion.values() if isinstance(v, np.ndarray)), None)
426
- return kp_drive
427
- else:
428
- return motion
429
-
430
- except Exception as e:
431
- logger.error(f"Motion parameter extraction failed: {e}")
432
- return None
433
 
434
  def _run_motion_for_image(self, img: np.ndarray):
435
  """Helper: run motion/keypoint extractor on an image and return structured outputs."""
 
63
 
64
  # Performance tracking
65
  self.inference_times = []
66
+ # Warmup sentinel
67
+ self._did_warmup = False
68
 
69
  def _get_onnx_providers(self) -> List[str]:
70
  """Get optimal ONNX execution providers. Enforce GPU if required."""
 
379
  return None
380
 
381
  def extract_motion_parameters(self, driving_image: np.ndarray) -> Optional[np.ndarray]:
382
+ """Extract motion parameters from driving image.
383
+ Returns keypoints/pose tensor shaped [1,K,3] if possible.
384
+ """
385
  if self.motion_session is None:
386
  logger.error("Motion model not loaded")
387
  return None
388
+ try:
389
+ motion = self._run_motion_for_image(driving_image)
390
+ # Prefer explicit kp_driving from motion model if available
391
+ if isinstance(motion, dict):
392
+ kp_drive = (motion.get('kp_driving') or motion.get('driving') or
393
+ motion.get('kp_drive') or motion.get('drive'))
394
+ if kp_drive is None:
395
+ # Fallback to first ndarray value
396
+ kp_drive = next((v for v in motion.values() if isinstance(v, np.ndarray)), None)
397
+ if kp_drive is None:
398
+ logger.error("Motion model outputs did not include a usable keypoint array")
399
+ return None
400
+ return kp_drive
401
+ else:
402
+ return motion
403
+ except Exception as e:
404
+ logger.error(f"Motion parameter extraction failed: {e}")
405
+ return None
406
 
407
  def warmup(self) -> bool:
408
  """Run a minimal generator inference with zero motion to prime kernels and caches.
 
435
  except Exception as e:
436
  logger.debug(f"Warmup skipped: {e}")
437
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438
 
439
  def _run_motion_for_image(self, img: np.ndarray):
440
  """Helper: run motion/keypoint extractor on an image and return structured outputs."""