Maskinöversättning · Engelskt original

API och protokoll

Native Apple runtime · PocketTTS

PocketTTS lägger till infödd CoreML talsyntes till speech-swift. Alla steg som standard för CPU-bara avrättning. Den engelska Alba förinställda strömmar 1,920-provramarna vid 24 kHz (80 ms per ram).

Swift och CLI-användning

import PocketTTS
let model = try PocketTTSModel.fromBundle(URL(fileURLWithPath: "/path/to/bundle"))
try await model.generateStreaming(text: "Hello world.") { chunk in
    await consume(chunk.samples)
}

Den AudioCommon modulen definierar modell-agnostiska protokoll och delade typer. Alla överensstämmelse modell kan användas omväxlande genom dessa gränssnitt.

Protokollöversikt

┌─────────────────────────────────────────────────────────┐
│                    AudioCommon                          │
│                                                         │
│  AudioChunk          SpeechGenerationModel (TTS)        │
│  AlignedWord         SpeechRecognitionModel (STT)       │
│  SpeechSegment       ForcedAlignmentModel               │
│                      SpeechToSpeechModel                │
│                      VoiceActivityDetectionModel (VAD)   │
│                      TurnCompletionProvider (EOT)       │
│                      SpeakerEmbeddingModel              │
│                      SpeakerDiarizationModel            │
│                      SpeakerExtractionCapable           │
└─────────────────────────────────────────────────────────┘

SpeechRecognitionModel

Protokoll för tal-till-text modeller.

public protocol SpeechRecognitionModel: AnyObject {
    var inputSampleRate: Int { get }
    func transcribe(audio: [Float], sampleRate: Int, language: String?) -> String
    func transcribeWithLanguage(audio: [Float], sampleRate: Int, language: String?) -> TranscriptionResult
}

Överensstämmande typer: Qwen3ASRModel, WhisperASRModel, ParakeetASRModel, ParakeetStreamingASRModel, OmnilingualASRModel (CoreML), OmnilingualASRMLXModel (MLX)

SpeechGenerationModel

Protokoll för text-till-tal modeller.

public protocol SpeechGenerationModel: AnyObject {
    var sampleRate: Int { get }
    func generate(text: String, language: String?) async throws -> [Float]
    func generateStream(text: String, language: String?) -> AsyncThrowingStream<AudioChunk, Error>  // has default impl
}

generateStream() har en standard implementering som wraps generate() som en enda bit. Modeller med sann streaming (t.ex. Qwen3-TTS) åsidosätter det.

Överensstämmande typer: Qwen3TTSModel, CosyVoiceTTSModel, VoxCPM2TTSModel, KokoroTTSModel, IndexTTS2TTSModel

IndexTTS2TTSModel lägger till en referens-audio generate överbelastning för noll-shot röst kloning och använder IndexTTS2SynthesisOptions för tal- och pauskontroller.

Qwen3-TTS lokal belastning

fromLocal validerar nödvändiga filer och checkpoint shard fullständighet, sedan härleder modellstorlek, kvantisering och arkitektur från config.json. Den valfria configuration värdet är bara en nedgång för utelämnad metadata; missbildad eller motsägelsefull metadata kastar Qwen3TTSLoadingError.invalidConfiguration

public static func fromLocal(
    modelDirectory: URL,
    tokenizerDirectory: URL,
    configuration: Qwen3TTSConfig? = nil,
    wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .none,
    progressHandler: ((Double, String) -> Void)? = nil
) throws -> Qwen3TTSModel

public static func fromPretrained(
    modelId: String = Qwen3TTSModel.defaultModelId,
    tokenizerModelId: String = "Qwen/Qwen3-TTS-Tokenizer-12Hz",
    cacheDir: URL? = nil,
    tokenizerCacheDir: URL? = nil,
    offlineMode: Bool = false,
    wiredMemoryPolicy: Qwen3TTSWiredMemoryPolicy = .pin(fraction: 0.9),
    progressHandler: ((Double, String) -> Void)? = nil
) async throws -> Qwen3TTSModel
Minnespolitik

fromLocal standarder till .none, så det ändrar inte process-wide Metal trådbunden minnesgräns. fromPretrained håller sin .pin(fraction: 0.9) standard. Båda pretrained lastare accepterar en separat tokenizerCacheDir

ForcedAlignmentModel

Protokoll för tidsstämpel på ordnivå.

public protocol ForcedAlignmentModel: AnyObject {
    func align(audio: [Float], text: String, sampleRate: Int, language: String?) -> [AlignedWord]
}

SpeechToSpeechModel

Protokoll för dialogmodeller för tal-till-tal.

public protocol SpeechToSpeechModel: AnyObject {
    var sampleRate: Int { get }
    func respond(userAudio: [Float]) -> [Float]
    func respondStream(userAudio: [Float]) -> AsyncThrowingStream<AudioChunk, Error>
}

Konformeringstyper: PersonaPlexModel

HibikiTranslateModel

Tal-till-tal översättningsförare. överensstämmer inte med SpeechToSpeechModel eftersom kontraktet är annorlunda: inmatning och utmatning är olika språk, utgångslängden är variabel (EOS-driven, kapad vid max(tSrc * 5/2, tSrc + 20) steg - ungefär 1.0-1.6 källtid på FLEURS-stil klipp), och det finns en parallell text-token ström bredvid ljudet. Full guide

public final class HibikiTranslateModel {
    public let cfg: HibikiConfig
    public static var defaultModelId: String { "aufklarer/Hibiki-Zero-3B-MLX-4bit" }
    public static var modelId8bit: String  { "aufklarer/Hibiki-Zero-3B-MLX-8bit" }

    public static func fromPretrained(
        modelId: String = defaultModelId,
        cacheDir: URL? = nil,
        offlineMode: Bool = false,
        progressHandler: ((Double, String) -> Void)? = nil
    ) async throws -> HibikiTranslateModel

    public func warmUp()

    public func translate(
        sourceAudio: [Float],
        sourceLanguage: HibikiSourceLanguage = .fr,
        verbose: Bool = false
    ) -> (audio: [Float], textTokens: [Int32])

    public func translateStream(
        sourceAudio: [Float],
        sourceLanguage: HibikiSourceLanguage = .fr,
        streaming: HibikiStreamingConfig = .default,
        verbose: Bool = false
    ) -> AsyncThrowingStream<AudioChunk, Error>
}

public enum HibikiSourceLanguage: String, CaseIterable, Sendable, Codable {
    case fr, es, pt, de
}

Provfrekvens: 24 kHz mono, både ingång och utgång. Frame rate: 12.5 Hz (Mimi codec). Källa språk: FR / ES / PT / DE. Målspråk: EN. Set HIBIKI_GREEDY=1 vid processlansering för deterministisk argmax avkodning (matchar CI-kanarier).

VoiceActivityDetectionModel

Protokoll för röstaktivitetsdetektering.

public protocol VoiceActivityDetectionModel: AnyObject {
    var inputSampleRate: Int { get }
    func detectSpeech(audio: [Float], sampleRate: Int) -> [SpeechSegment]
}

TurnCompletionProvider

Protokoll för end-of-turn klassificerare konsulterade av StreamingVADProcessor på varje bekräftad VAD paus. En VAD hör bara tystnad; en turn-completion modell lyssnar på prosody av hela yttrandet, så en mitten av tystnad paus håller agenten väntar medan en färdig mening får ett omedelbart svar. Kartor till speech-core sc_turn_completion_vtable_tSe den Smart Turn guide.

public protocol TurnCompletionProvider: AnyObject {
    /// Probability in [0, 1] that the turn is complete, given the audio of the
    /// turn so far (Smart Turn looks at the last 8 s).
    func turnCompleteProbability(audio: [Float], sampleRate: Int) throws -> Float
}

Konformeringstyper: SmartTurnModel (Smart Turn v3.2)

SpeakerEmbeddingModel

Protokoll för högtalare som bäddar in extraktion.

public protocol SpeakerEmbeddingModel: AnyObject {
    var inputSampleRate: Int { get }
    var embeddingDimension: Int { get }
    func embed(audio: [Float], sampleRate: Int) -> [Float]
}

Konformeringstyper: WeSpeakerModel

SpeakerDiarizationModel

Protokoll för högtalardiariseringsmodeller som tilldelar högtalaretiketter till ljudsegment.

public protocol SpeakerDiarizationModel: AnyObject {
    var inputSampleRate: Int { get }
    func diarize(audio: [Float], sampleRate: Int) -> [DiarizedSegment]
}

Konformeringstyper: DiarizationPipeline (Pyannote), SortformerDiarizer

SpeakerExtractionCapable

Utökat diariseringsprotokoll för motorer som stöder extrahera en målhögtalares segment med hjälp av en referensinbäddning. Inte alla motorer stöder detta (Sortformer är end-to-end och producerar inte högtalarinbäddningar).

public protocol SpeakerExtractionCapable: SpeakerDiarizationModel {
    func extractSpeaker(audio: [Float], sampleRate: Int, targetEmbedding: [Float]) -> [SpeechSegment]
}

Konformeringstyper: DiarizationPipeline (Pyannote endast)

Delade Typer

AudioChunk

public struct AudioChunk {
    public let samples: [Float]   // PCM samples
    public let sampleRate: Int    // Sample rate (e.g. 24000)
}

SpeechSegment

public struct SpeechSegment {
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
}

AlignedWord

public struct AlignedWord {
    public let text: String       // The word
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
}

DiarizedSegment

public struct DiarizedSegment {
    public let startTime: Float   // Start time in seconds
    public let endTime: Float     // End time in seconds
    public let speakerId: Int     // Speaker identifier (0-based)
}

Dialogsegment

Ett parsed segment av multi-speaker dialog text med valfria högtalare och känslor taggar. Används med DialogueParser och DialogueSynthesizer för CosyVoice3 dialog syntes.

public struct DialogueSegment: Sendable, Equatable {
    public let speaker: String?   // Speaker identifier ("S1", "S2"), nil for untagged
    public let emotion: String?   // Emotion tag ("happy", "whispers"), nil if none
    public let text: String       // Cleaned text to synthesize
}

DialogParser

Parses multi-speaker dialog text med inline högtalare taggar ([S1]) och känslor taggar ( (happy)).

public enum DialogueParser {
    static func parse(_ text: String) -> [DialogueSegment]
    static func emotionToInstruction(_ emotion: String) -> String
}

Inbyggda känslor: happy/excited, sad, angry, whispers/whispering, laughs/laughing, calm, surprised, seriousOkända taggar passerar genom som friformsinstruktioner.

DialogueSynthesizer

Orkestrerar multi-segment dialog syntes med per-talare röst kloning, tystnad luckor och crossfade.

public enum DialogueSynthesizer {
    static func synthesize(
        segments: [DialogueSegment],
        speakerEmbeddings: [String: [Float]],
        model: CosyVoiceTTSModel,
        language: String,
        config: DialogueSynthesisConfig,
        verbose: Bool
    ) -> [Float]
}

DialogueSynthesisConfig

public struct DialogueSynthesisConfig: Sendable {
    public var turnGapSeconds: Float      // Default: 0.2
    public var crossfadeSeconds: Float    // Default: 0.0
    public var defaultInstruction: String // Default: "You are a helpful assistant."
    public var maxTokensPerSegment: Int   // Default: 500
}

PipelineLLM

Protokoll för språkmodellintegration med röstpipelines. Bridges an LLM till VoicePipelines ASR → LLM TTS flöde.

public protocol PipelineLLM: AnyObject {
    func chat(messages: [(role: MessageRole, content: String)],
              onToken: @escaping (String, Bool) -> Void)
    func cancel()
}

Built-in adapter: Qwen3PipelineLLM broar Qwen35MLXChat till detta protokoll med token cleanup, avbokning och väntande frasackumulering.

AudioIO

Återanvändbar ljud I / O-hanterare som eliminerar AVAudioEngine-pannan. Handles mic capture, återförsäljning, uppspelning och ljudnivåmätning.

let audio = AudioIO()
try audio.startMicrophone(targetSampleRate: 16000) { samples in
    pipeline.pushAudio(samples)
}
audio.player.scheduleChunk(ttsOutput)
audio.stopMicrophone()

AudioIO innehåller en StreamingAudioPlayer för TTS utgång och en AudioRingBuffer för trådsäker ljudöverföring mellan fångst och inferenstrådar.

SystemAudioTap

Fångar systemutgångsmixen - vad Mac spelar för närvarande - som mono Float32 vid en caller-chosen-provfrekvens (Core Audio process kranar, macOS 14.4 +) Mono global kran exkluderar den nuvarande processen som standard, så en app egen uppspelning är aldrig återfångad, och den är inslagen i en privat sammanlagd enhet som bara innehåller kranen.

let tap = SystemAudioTap()
try tap.start(targetSampleRate: 16000) { samples in
    pipeline.pushAudio(samples)
}
tap.stop()

Fånga överlever standard-output-enhetsbrytare - den interna återförsäljaren är ombyggd när blandningsfrekvensen ändras. värdappen måste förklara NSAudioCaptureUsageDescription. Om inspelningstillståndet nekas, kan en kran fortfarande skapas men leverera endast tystnad: titta på framesCaptured medan nonSilentFrames stannar 0 och misslyckas stängd.

Timestamped oberoende källor

Båda fånga klasser exponera tidsstämplade överbelastningar. CapturedAudioChunk bär mono samples vid sampleRate plus en valfri Mach hostTime för den första ingångsramen. Eftersom båda källorna använder samma värdklocka kan en app hålla sin PCM separat och order härledda transkripshändelser utan att blanda ljudet.

try tap.startTimestamped(targetSampleRate: 16000) { systemChunk in
    systemPipeline.pushAudio(systemChunk.samples)
    recordSystemTime(systemChunk.hostTime)
}

let microphone = AudioIO(enableAEC: true, enablePlayback: false)
try microphone.startMicrophoneTimestamped(targetSampleRate: 16000) { micChunk in
    microphonePipeline.pushAudio(micChunk.samples)
    recordMicrophoneTime(micChunk.hostTime)
}

För att endast fånga upp enableAEC: true aktiverar Apple Voice Processing innan mikrofonformatet läses, och enablePlayback: false utelämnar den oanvända spelaren. På macOS tas enhetsuppspelningen bort från mikrofonprover och misslyckande att starta AEC rapporteras istället för att falla tillbaka till rå ingång.

Incremental file input och erkännande

CapturedAudioChunk Har också en monotonic frameIndex och en isFinal markör. AudioFileLoader.stream producerar dessa bitar på efterfrågan med begränsat minne, återförsäljning en gång med ihållande konvertertillstånd, och i genomsnitt alla ingångskanaler som standard. .first eller .select([indices]) När kanalen routing är känd.

StreamingRecognitionModel och StreamingRecognitionSession ger en cache-bevara inkrementell API över Parakeet Streaming och Nemotron Core ML/MLX

let source = AudioFileLoader.stream(
    url: inputURL,
    options: AudioFileStreamOptions(targetSampleRate: 16_000,
                                    chunkDuration: 0.32,
                                    channelSelection: .mixAll))
let session = try model.makeStreamingSession(language: "en-US")

for try await chunk in source {
    for update in try session.push(chunk) {
        render(update.text, final: update.isFinal)
    }
}
for update in try session.finish() {
    render(update.text, final: true)
}

SentencePieceModel

Shared protobuf-läsare för SentencePiece .model filer, bor i AudioCommon. Varje modul som behöver avkoda Sentencepice-stycken (PersonaPlex, OmnilingualASR, framtida ASR/TTS portar TTS

public struct SentencePieceModel: Sendable {
    public struct Piece: Sendable, Equatable {
        public let text: String
        public let score: Float
        public let type: Int32
        public var pieceType: PieceType? { get }
        public var isControlOrUnknown: Bool { get }
    }
    public enum PieceType: Int32 {
        case normal = 1, unknown = 2, control = 3,
             userDefined = 4, unused = 5, byte = 6
    }
    public let pieces: [Piece]
    public var count: Int { get }
    public subscript(_ id: Int) -> Piece? { get }
    public init(contentsOf url: URL) throws
    public init(modelPath: String) throws
    public init(data: Data) throws
}

Used by: OmnilingualASR.OmnilingualVocabulary, PersonaPlex.SentencePieceDecoder. Täckt av 7 enhetstester i Tests/AudioCommonTests/SentencePieceModelTests.

MLXCommon.SDPA

Scaled dot-produkt uppmärksamhetshjälpare delas över varje MLX uppmärksamhet modul (Qwen3-ASR / Qwen3-TTS / Qwen3-Chat / CosyVoice / PersonaPlex / OmnilingualASR) Varje modul håller sina egna prognoser - SDPA hanterar bara omformningen → uppmärksamhet → sammanfoga pannorplattan.

public enum SDPA {
    // Flat [B, T, H*D] input: project/reshape happens inside
    public static func multiHead(
        q: MLXArray, k: MLXArray, v: MLXArray,
        numHeads: Int, headDim: Int, scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // GQA / MQA variant with separate query and KV head counts
    public static func multiHead(
        q: MLXArray, k: MLXArray, v: MLXArray,
        numQueryHeads: Int, numKVHeads: Int, headDim: Int, scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // Already-shaped [B, H, T, D] (RoPE / KV cache paths)
    public static func attendAndMerge(
        qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
        scale: Float,
        mask: MLXArray? = nil
    ) -> MLXArray

    // Same, with ScaledDotProductAttentionMaskMode enum (newer API)
    public static func attendAndMerge(
        qHeads: MLXArray, kHeads: MLXArray, vHeads: MLXArray,
        scale: Float,
        mask: MLXFast.ScaledDotProductAttentionMaskMode
    ) -> MLXArray

    // Low-level head merge: [B, H, T, D] → [B, T, H*D]
    public static func mergeHeads(_ attn: MLXArray) -> MLXArray
}

Alla omformningssamtal använder -1 för satsdimensionen så att hjälparna komponerar med MLX.compile(shapeless:) grafer som varierar sats vid runtime (t.ex. Qwen3-TTS

HTTP API Server

Den som speech-server binär exponerar varje modell i speech-swift som HTTP REST endpoints plus en WebSocket slutpunkt som implementerar OpenAI realtid API. Modeller laddas lat på första begäran; pass --preload att värma dem alla i startup.

swift build -c release
.build/release/speech-server --port 8080

# Preload every model at startup
.build/release/speech-server --port 8080 --preload

REST Endpoints

EndpointMetodBegäranResponse
/transcribePOSTaudio/wav kroppJSON { text } (Qwen3-ASR)
/v1/audio/transcriptionsPOSTMultipart { file, model, response_format?, language?, prompt?, temperature? }JSON, text, verbose JSON , SRT eller VTT
/v1/audio/speechPOSTJSON { model, input, voice, response_format?, speed? }audio/wav eller audio/pcm kropp
/speakPOSTJSON { text, engine?, language?, voice? }audio/wav kropp (Qwen3-TTS, CosyVoice och Kokoro)
/respondPOSTaudio/wav kroppaudio/wav kropp (PersonaPlex)
/enhancePOSTaudio/wav kroppaudio/wav kropp (DeepFilterNet3)
/vadPOSTaudio/wav kroppJSON segment lista
/diarizePOSTaudio/wav kroppJSON DiarizedSegment lista
/embed-speakerPOSTaudio/wav kroppJSON [Float] (256-dim)

/v1/audio/speech kartlägger standard TTS modell och röstnamn till lokala motorer. Det är standard för WAV; response_format: "pcm" returnerar headerless 24 kHz mono PCM16 lite-endian ljud. Unsupported compressed formats avvisas explicit.

/v1/audio/transcriptions accepterar en WAV-fil som multipart-formdata och standarder för att {"text":"..."}; response_format kan också begära text, verbose JSON, SRT eller VTT. På Apple väljer modellalias en lokal ASR motor. förpackade speech-core Server på Linux och Windows använder lat laddad Parakeetaccepterar PCM16/24/32 eller Float32 WAV, och caps laddas upp på 25 MiB och 10 minuter.

# Transcribe a file
curl -X POST http://localhost:8080/transcribe \
  --data-binary @recording.wav \
  -H "Content-Type: audio/wav"

# Synthesize speech
curl -X POST http://localhost:8080/speak \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world", "engine": "cosyvoice"}' \
  -o output.wav

# OpenAI-compatible transcription
curl http://localhost:8080/v1/audio/transcriptions \
  -F "[email protected];type=audio/wav" \
  -F "model=whisper-1"

# OpenAI-compatible speech synthesis
curl http://localhost:8080/v1/audio/speech \
  -H "Content-Type: application/json" \
  -d '{"model":"tts-1","voice":"alloy","input":"Hello world","response_format":"wav"}' \
  -o openai-output.wav

# Full speech-to-speech round trip
curl -X POST http://localhost:8080/respond \
  --data-binary @question.wav \
  -o response.wav

OpenAI Realtime API (/v1/realtime)

WebSocket endpoint vid ws://host:port/v1/realtime implementerar OpenAI Realtime-protokollet. Alla meddelanden är JSON med en type diskriminator; ljudbelastningar är base64-encoded PCM16 vid 24 kHzo.

Under kalla modellbelastningar eller långa generationer avger servern lätta realtime.keepalive JSON-händelser och webbsocketpongkontrollramar ungefär var 15 sekunder tills modellutgången är klar. Kunder kan ignorera dessa händelser eller behandla dem som aktivitetsindikatorer.

Kund → Server händelser

Eventsyftet
session.updateKonfigurera motor, språk, röst, ljudformat och valfri server VAD
input_audio_buffer.appendAppendera en base64 PCM16 bit till ingångsbufferten
input_audio_buffer.commitBegå buffert ljud för transkription
input_audio_buffer.clearKassera den aktuella ingångsbufferten
response.createBegär TTS syntes för den levererade texten/instruktionen

Server → Kundevenemang

EventBetydelse
session.createdHandshake komplett, standard config emitted
session.updatedSenaste session.update erkänd
input_audio_buffer.speech_startedServer VAD upptäckte början på en talvridning
input_audio_buffer.speech_stoppedServer VAD avslutade svängen efter tystnad eller varaktighetsgränsen
input_audio_buffer.committedAudio accepterad och köad för transkription
conversation.item.input_audio_transcription.completedASR resultat med slutlig transkripttext
response.audio.deltaBase64 PCM16 bit av syntetiserad ljud
response.audio.doneInga fler ljudbitar för detta svar
response.doneResponse finalized (metadata + latensstatistik)
errorFelkuvert med type och message

Automatiska svängningar med server VAD

Explicit input_audio_buffer.commit förblir standarden. För att låta servern stänga och transkribera vänder automatiskt med Silero VAD, konfigurera turn_detectionServern håller bara den begärda pre-rollen medan tomgång, avger talstart / stopp och begår händelser i ordning, och tvinga stänger en kontinuerligt aktiv vändning på max_turn_duration_ms.

ws.send(JSON.stringify({
  type: 'session.update',
  session: {
    turn_detection: {
      type: 'server_vad',
      threshold: 0.5,
      prefix_padding_ms: 300,
      silence_duration_ms: 500,
      max_turn_duration_ms: 120000
    }
  }
}));

// Disable automatic turns and return to explicit commits:
// { type: 'session.update', session: { turn_detection: null } }
const ws = new WebSocket('ws://localhost:8080/v1/realtime');

// ASR: push audio, request transcription
ws.send(JSON.stringify({ type: 'input_audio_buffer.append', audio: base64PCM16 }));
ws.send(JSON.stringify({ type: 'input_audio_buffer.commit' }));
// → conversation.item.input_audio_transcription.completed

// TTS: request synthesis and stream audio deltas
ws.send(JSON.stringify({
  type: 'response.create',
  response: { modalities: ['audio', 'text'], instructions: 'Hello world' }
}));
// → response.audio.delta (repeated), response.audio.done, response.done

Servern bor i AudioServer SPM-produkten. Ett exempel på webbläsarens klient skickas på Examples/websocket-client.html - öppna den tillsammans med en löparserver för att köra hela ASR + TTS rundresa.

Modell nedladdningar

Alla modeller laddas ner från HuggingFace vid första användning och cachad i ~/Library/Caches/qwen3-speech/. AudioCommon-modulen ger en delad HuggingFaceDownloader som hanterar nedladdning, cache och integritetsverifiering.

Ladda ner retries

Modelllastare och nedladdning API accepterar downloadOptions: DownloadOptions. onRetry tar emot en DownloadRetryEvent före varje backoff, med modelId, attempt, maxAttempts, originalet erroroch retryInSeconds. nil betyder att inga retries kvar; fullständig-cache-nedgång kan fortfarande lyckas.

Callbacks är synkrona och @Sendable, utan huvudaktörsgaranti. Avbokning producerar ingen retry händelse. retryDelaysSeconds att anpassa policyn ([] betyder ett försök); den explicita nedladdningen API argument tar företräde. Standard försök räknas och byte framsteg bevaras. misslyckade försök är också inloggade på noteringsnivå genom att AudioLog.download.

Kooperativ avbokning

För MLX transkription, Qwen3ASRModel.transcribeCheckingCancellation(audio:sampleRate:options:) kastar CancellationError när uppgiftsavbokning observeras före funktionsutvinning, kodning, dekoderprefill eller ett avkodningssteg. GPU-arbete som redan lämnats in kan avslutas. transcribe och transcribeBatch API behåller sitt icke-kastande beteende och ignorerar uppgiftsavbokning. speech-server använder annulleringsmedveten ingångspunkt för Qwen3-ASR.

MOSS-TTS-Nano

MossTTSNanoModel serialiserar förfrågningar och skapar färska CoreML tillstånd för varje yttrande. syntetisera och synthesizeStream bevara stereo; SpeechGenerationModel returnerar en mono downmix. Pass språk: nil. Streams slut med en tom slutmarkör.

import AudioCommon
import MossTTSNano

let model = try await MossTTSNanoModel.fromPretrained()
let reference = try await model.encodeReference(url: referenceURL)
let result = try await model.synthesize(
    text: "The weather is nice today.", reference: reference,
    options: MossTTSNanoOptions(maxFrames: 300, seed: 1))
try WAVWriter.writeStereo(left: result.left, right: result.right,
    sampleRate: 48000, to: outputURL)

MOSS-TTS-Nano

Lokal inhemsk slutsats

För 12B, upprepade intilliggande verktygslåda stängning markörer omedelbart efter en fullständig, validerad samtal konsumeras med det samtalet. De skapar inte ett annat samtal eller ändrar sina argument. Standalone markörer och missbildade argument förbli fel; E4B behåller strikt hantering.

För att behålla misslyckade inhemska chattförfrågningar, ange failureCaptureDirectory i slutsatsen manifest. Capture är av som standard. Katalogen måste neka grupp och annan åtkomst; nya kataloger använder 0700 och misslyckande filer använder 0600Filer inkluderar den exakta förfrågan organ, begära ID och fel. Slutförda 12B generationer behåller också de återgivna snabb, token ID, rå utgång och stoppa anledning innan parsing. E4B fel har ingen rå generation rekord. Dessa lokala filer innehåller konversation och verktyg innehåll; HTTP fel och framgångsrika förfrågningar inte inkludera dem.

Gemma4UnifiedChat.generateResponse accepterar en valfri onGeneration callback med en Gemma4GenerationTrace. Uppmaningen går före tool parsing, inklusive när den slutförandet avvisas. Inspelningen ändrar inte provtagning eller accepterad verktygssyntax.

Optionell agentDirectory laddar en iscensatt Gemma 4 12B Unified INT4 textmodell tillsammans E4B. Använd modell: "gemma4-12b-it"för chatt med inhemska verktyg, resultathistorik och avbokning. E4B förblir tillgänglig för andra förfrågningar. Gemma4UnifiedChat Använder Pinned MLX text implementering och släpper varje förfrågan KV cache. Response parsing tar bort kanalavgränsare från returnerad text. Föräldralösa verktygsuppsägare avvisas som missbildade svar. För Gemma 4 12B, verktygsmeddelandeinnehåll bevarar JSON som text, som matchar checkpoint chat mall.

Källan bygger acceptera --inference-manifest modeller.json att ladda iscensatt Gemma 4 E4B INT4, VoxCPM2full, full Whisper Large-v3och Silero VADDenna tjänst startar offline och avvisar oförenliga modellidentiteter.

VoxCPM2 stöder viktPrecision: .float16 för en okvantiserad kontrollpunkt. Dess huvudparametrar använder FP16; AudioVAE och viss beräkning förblir FP32. INT8 checkpoints avvisas för detta läge.

Gemma4ConversationMessage, Gemma4Tool och genereraResponse bevara inhemska verktygsdeklarationer, argument och resultathistorik. InferenceCancellationToken avbryter generationen kooperativt. Silero makeIndependentStream isolerar tillstånd och processChunkThrowing förökar inferensfel.

Whisper ASRModel.fromDirectory laster iscensatt full Large-v3 CoreML buntar. Den 32-lagers dekodern kan bygga sin prefix utan TextDecoderContextPrefill.mlmodelc och släpper per-stegs temporaries. Standard Whisper CLI-modellen förblir Turbo.

Den infödda tjänsten serialiserar chatt, syntes och transkription; lätt VAD har separat strömtillstånd. Gemma prefill använder 256-token-satser, begär KV-tillståndet släpps, och MLX cache är capped på 128 MiB med en 20 GiB-allokatorgräns.

Tal med ström: sann returnerar NDJSON ljudhändelser vid 48 kHz, följt av gjort eller fel. Avbryt genom requestID på /v1 / slutsats / avbryt. GET /v1/inference/capabilities och/v1/Inference/provenance report model identities, parameter dtypes, asset pins och minnesräknare.

speech-server --inference-manifest models.json --port 8080
POST /v1/inference
{"operation":"chat","model":"gemma4-e4b-it","requestID":"chat-1","messages":[{"role":"user","content":"Hello"}],"maxTokens":128,"temperature":0}

chat: gemma4-e4b-it
speech: voxcpm2
transcribe: whisper-large-v3
vad: silero-vad

Audio: audioBase64 (mono PCM16), sampleRate
VAD: streamID, reset, endStream; 512 samples/frame at 16000 Hz
Tools: name, description, parameters
Tool call: id, name, arguments
Tool result: role="tool", toolCallID, content

Valfri modell ägande

SpeechRuntime tillhandahåller delad lastning, avbokningsbar FIFO-antagning och serialiserad avrättning. Qwen3ASRRuntime väljer MLX eller full CoreML genom explicita deskriptorer; inhemska API och CLI-flaggor finns tillgängliga.

Ljudservern har ett genomförandetillstånd genom varje fullständig förfrågan, inklusive strömmat utgång, med upp till 32 förfrågningar som väntar. ASR fel ger HTTP-fel eller WebSocket felhändelser; giltiga tomma utskrifter förblir framgångsrika. Playback bevarar överflödesprover. Cachade modeller och pending uppspelningsljud upptar fortfarande minne.

import Qwen3ASRRuntime

let runtime = Qwen3ASRRuntime(descriptor: Qwen3ASRDescriptors.small)
let text = try await runtime.transcribe(audio: samples, sampleRate: 16000)
try await runtime.unload()

Voxtral TTS

Voxtral 4B TTS 2603 körs lokalt på Apple Silicon genom Voxtral TTS-modulen: 20 förinställda röster, nio språk och 24 kHz monotal.

import VoxtralTTS

let model = try await VoxtralTTSModel.fromPretrained()
for try await chunk in model.generateStream(text: "Hello, welcome to Voxtral.", voice: "casual_male") {
    print(chunk.frameIndex, chunk.samples.count, chunk.isFinal)
}

Förinställda röster endast; referens-audio kloning är otillgänglig. Audio utgångsströmmar genom SDK, CLI, WebSocket och HTTP PCM. Supply the fullständig input text up front. Model vikter använder CC BY-NC 4.0.

import AudioCommon

let player = StreamingAudioPlayer()
player.skipInitialSilence = false

Läs Voxtral TTS guide