# How to Debug Diarization Issues with Speaker Embedding Validation in FluidAudio

> Debug diarization issues in FluidAudio by validating speaker embeddings. Enable debug logging and check embedding magnitudes for common errors. Resolve inconsistencies and improve speaker recognition accuracy.

- Repository: [Fluid Inference/fluidaudio](https://github.com/fluidinference/fluidaudio)
- Tags: how-to-guide
- Published: 2026-03-02

---

**Enable `AppLogger` debug mode, inspect `AudioValidation.validateEmbedding` rejection logs, and verify that embedding magnitudes exceed the 0.1 threshold while containing no NaN or Inf values.**

When working with the FluidAudio open-source diarization pipeline, invalid speaker embeddings are the most common root cause of missing or mis-assigned speaker labels. The system implements a three-layer validation architecture that checks every embedding before it reaches the clustering stage. Understanding how to debug diarization issues with speaker embedding validation requires tracing these validation layers, interpreting log output, and verifying vector integrity at each processing step.

## Understanding the Three Validation Layers

FluidAudio validates speaker embeddings at three distinct architectural levels. Each layer serves a different integration point, from low-level audio processing to public API exposure.

### Raw Embedding Check in AudioValidation

The foundational validation occurs in [`Sources/FluidAudio/Diarizer/Segmentation/AudioValidation.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Segmentation/AudioValidation.swift). The `validateEmbedding(_:)` method enforces three strict constraints:

- The vector must be non-empty
- All values must be finite (no NaN or Inf)
- The vector magnitude must exceed **0.1**

If any check fails, the pipeline logs a specific error and discards the segment before clustering begins.

### Public API Check via SpeakerUtilities

For external callers and test suites, [`Sources/FluidAudio/Diarizer/Clustering/SpeakerOperations.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Clustering/SpeakerOperations.swift) exposes `SpeakerUtilities.validateEmbedding(_:minMagnitude:)`. This function provides the same core logic as `AudioValidation` but allows customization of the magnitude threshold via the `minMagnitude` parameter.

### Diarizer Manager Wrapper

At the highest level, [`Sources/FluidAudio/Diarizer/Core/DiarizerManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Core/DiarizerManager.swift) implements `DiarizerManager.validateEmbedding(_:)`. This wrapper automatically invokes the `AudioValidation` checks before passing embeddings to the clustering algorithms, ensuring no invalid vectors reach the speaker assignment logic.

## Common Failure Scenarios

Identifying the specific symptom helps isolate which validation layer is rejecting your embeddings.

| Symptom | Likely Validation Failure | Root Cause |
|---------|--------------------------|------------|
| **All segments have empty speaker IDs** | `AudioValidation.validateEmbedding` returns `false` | Embedding magnitude ≤ 0.1 due to silence, silent frames, or badly-scaled model output |
| **Intermittent "Embedding invalid" warnings** | NaN/Inf values detected | Model divergence, numerical overflow, or corrupted mask input |
| **No diarization output at all** | `audioValidation.validateAudio` fails before embedding generation | Audio shorter than 1 second, RMS energy < 0.01, or empty buffer |
| **Speaker merge/assign fails on long recordings** | FIFO raw-embedding queue capacity exceeded | `SpeakerUtilities.addRawEmbedding` enforces a 50-item limit; moving average decays below threshold when many short segments are added |

## Step-by-Step Debugging Workflow

Follow this systematic approach to isolate and resolve embedding validation failures.

### Enable Debug Logging

Increase the log verbosity to capture validation rejection details:

```swift
import FluidAudio

// Set the logger to debug level in your app entry point
AppLogger.setLogLevel(.debug)

```

With debug logging active, you will see specific diagnostic messages:
- `❗️ Empty embedding`
- `⚠️ Low magnitude embedding: 0.045`
- `❗️ Embedding contains NaN or Inf`

### Capture and Analyze Logs

Run the diarizer with the debug flag to generate detailed output:

```bash
swift run FluidAudioCLI diarize --audio path/to/file.wav --debug

```

The `--debug` flag (passed to `DiarizerConfig`) writes the `speakerDatabase` and timing data to disk, allowing you to inspect which embeddings survived the validation stage.

### Inspect Invalid Embeddings

When logs indicate a validation failure, dump the raw vector for manual inspection:

```swift
if !diarizer.validateEmbedding(embedding) {
    print("Invalid embedding (first 10 values):", embedding.prefix(10))
}

```

### Verify Magnitude and Finite Values

Manually compute the validation metrics to confirm the failure mode:

```swift
let magnitude = sqrt(embedding.map { $0 * $0 }.reduce(0, +))
print("Magnitude:", magnitude)               // Should be > 0.1
print("Finite? :", embedding.allSatisfy { $0.isFinite })

```

If the magnitude is below the default `0.1` threshold, you have two options:

1. **Adjust the threshold** (experimental/debug only) via `SpeakerUtilities.validateEmbedding(_:minMagnitude:)`.
2. **Check the upstream mask** – a noisy mask yields almost-silence in the extracted segment. Inspect the mask array printed from `processChunkWithSpeakerTracking` around the problematic timestamp.

### Unit Test the Validation Path

Create a targeted test to reproduce each failure mode:

```swift
func testEmbeddingValidation() {
    // Empty vector
    XCTAssertFalse(SpeakerUtilities.validateEmbedding([]))
    
    // NaN contamination
    XCTAssertFalse(SpeakerUtilities.validateEmbedding([Float.nan, 0.1, 0.2]))
    
    // Low magnitude
    let low = Array(repeating: 0.001, count: 256)
    XCTAssertFalse(SpeakerUtilities.validateEmbedding(low))
    
    // Valid embedding
    let good = (0..<256).map { Float($0) / 256.0 }
    XCTAssertTrue(SpeakerUtilities.validateEmbedding(good))
}

```

Run the test with:

```bash
swift test --filter SpeakerOperationsTests.testEmbeddingValidation

```

This confirms the validation logic itself is functioning correctly, isolating the issue to your specific audio input or model output.

## Practical Code Examples

### Directly Validate an Embedding

```swift
import FluidAudio

let embedding: [Float] = obtainEmbeddingFromModel()
let isValid = DiarizerManager(config: .default).validateEmbedding(embedding)

print(isValid ? "✅ Embedding OK" : "❌ Embedding rejected")

```

### Simulate a Low-Magnitude Embedding

```swift
let lowMag = Array(repeating: 0.02, count: 256)   // magnitude ≈ 0.32
let manager = DiarizerManager()
print(manager.validateEmbedding(lowMag))          // → false

```

### Override the Magnitude Threshold (Debug Only)

```swift
let customThreshold: Float = 0.05
let ok = SpeakerUtilities.validateEmbedding(lowMag, minMagnitude: customThreshold)
print("Custom threshold accepted? \(ok)")

```

### Hook Into the Diarizer Pipeline

```swift
let diarizer = DiarizerManager()
diarizer.initialize(models: myModels)

// Enable verbose logging
AppLogger.setLogLevel(.debug)

do {
    let result = try diarizer.performCompleteDiarization(audioSamples)
    // Inspect result.segments – any segment with empty speakerId indicates failed validation
    for seg in result.segments where seg.speakerId.isEmpty {
        print("❗️ Invalid segment at \(seg.startTimeSeconds)s – check embedding")
    }
} catch {
    print("Diarization failed:", error)
}

```

## Quick Debugging Checklist

| Step | Action |
|------|--------|
| 1️⃣ | Enable `AppLogger` debug level or use `--debug` CLI flag. |
| 2️⃣ | Search logs for "Empty embedding", "Low magnitude embedding", or "Embedding contains NaN or Inf". |
| 3️⃣ | Print the offending embedding vector and compute its magnitude manually. |
| 4️⃣ | Verify the mask used for extraction contains sufficient active frames in `processChunkWithSpeakerTracking`. |
| 5️⃣ | If magnitudes are consistently low, adjust `minMagnitude` **only for experimentation** via `SpeakerUtilities.validateEmbedding`. |
| 6️⃣ | Run `SpeakerOperationsTests.testEmbeddingValidation` to confirm validation logic integrity. |
| 7️⃣ | Re-run with `--debug` to inspect the full speaker database and confirm valid embeddings reach clustering. |

## Summary

Debugging diarization issues with speaker embedding validation in FluidAudio requires tracing three validation layers: the raw check in `AudioValidation.validateEmbedding`, the public API in `SpeakerUtilities.validateEmbedding`, and the high-level wrapper in `DiarizerManager.validateEmbedding`. 

- **Enable debug logging** via `AppLogger.setLogLevel(.debug)` to capture rejection reasons.
- **Inspect the embedding vector** manually when validation fails—check for magnitude > 0.1 and finite values.
- **Validate the upstream mask** in `processChunkWithSpeakerTracking` to ensure sufficient audio energy reaches the embedding extractor.
- **Unit test** the validation path using `SpeakerOperationsTests` to isolate logic errors from data issues.

## Frequently Asked Questions

### What causes "Low magnitude embedding" warnings in FluidAudio?

This warning appears when `AudioValidation.validateEmbedding` detects a vector with a magnitude below the default threshold of 0.1. This typically occurs when processing silent audio segments, frames with very low energy, or when the upstream voice activity detection (VAD) produces a mask with insufficient active frames. Check the mask output from `processChunkWithSpeakerTracking` around the timestamp mentioned in the logs to confirm sufficient audio content exists.

### How do I adjust the embedding validation threshold for debugging?

While the default threshold of 0.1 is hardcoded in `AudioValidation`, you can override it for experimental purposes using `SpeakerUtilities.validateEmbedding(_:minMagnitude:)`. Pass a custom `Float` value for `minMagnitude` to relax or tighten the constraint. Note that this should only be used for debugging—permanently lowering the threshold may allow noisy or silent embeddings into the clustering stage, degrading diarization accuracy.

### Why are all speaker IDs empty after running diarization?

Empty speaker IDs indicate that `DiarizerManager.validateEmbedding` rejected every embedding before clustering began. This cascade failure usually stems from `AudioValidation.validateAudio` rejecting the input audio first (due to duration < 1 second or RMS energy < 0.01), or from the embedding extractor producing consistently low-magnitude vectors. Enable `AppLogger.setLogLevel(.debug)` and re-run to identify whether the failure occurs at the audio validation or embedding validation stage.

### Where is the speaker embedding validation logic located in the source code?

The core validation logic resides in [`Sources/FluidAudio/Diarizer/Segmentation/AudioValidation.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Segmentation/AudioValidation.swift) within the `validateEmbedding(_:)` method. The public API wrapper is found in [`Sources/FluidAudio/Diarizer/Clustering/SpeakerOperations.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Clustering/SpeakerOperations.swift) as `SpeakerUtilities.validateEmbedding(_:minMagnitude:)`. The high-level integration point that automatically invokes validation before clustering is [`Sources/FluidAudio/Diarizer/Core/DiarizerManager.swift`](https://github.com/fluidinference/fluidaudio/blob/main/Sources/FluidAudio/Diarizer/Core/DiarizerManager.swift) in the `validateEmbedding(_:)` method.