Managing Model Files
Picking, downloading, and compiling models in your application
Picking the Model
Model Gallery lists all models supported by Argmax SDK. The following sections summarize considerations when picking the best speech-to-text model for your application.
Custom Models. If you need to bring your own custom model checkpoints for supported architectures, please see Bring Your Own Models, or reach out to Argmax on your Slack support channel or over email.
Nvidia Parakeet v2
This model is 9x faster than Whisper Large v3 Turbo on English speech-to-text and achieves slightly higher accuracy.
We recommend using this model for all applications that are English-only.
let config = WhisperKitProConfig(
model: "parakeet-v2_476MB",
modelRepo: .parakeetRepo
)iOS must use compressed models. Please use parakeet-v2_476MB instead of
parakeet-v2 for iOS apps. This compressed model is benchmarked and verified
to achieve an accuracy within 0.5% of the original non-compresssed model.
Nvidia Parakeet v3
This model achieves the same speed as Nvidia Parakeet v2 but supports 25 European languages with dynamic switching: en, de, es, fr, nl, it, da, et, fi, el, hu, lv, lt, mt, pl, pt, ro, sk, sl, sv, ru, uk, bg, hr, cs.
We recommend using this model for all applications that require at least one of the 25 languages from above other than the English-only scenario.
let config = WhisperKitProConfig(
model: "parakeet-v3_494MB",
modelRepo: .parakeetRepo
)Qwen3-ASR
This model achieves the highest accuracy available in Argmax SDK, at a significant cost in size and speed. It is the top ranking model on the private data track of the OpenASR Leaderboard Link as of July 2026, and Argmax SDK brings it to the familiar WhisperKitPro APIs with the full feature set.
This model supports 30 languages with dynamic switching: zh, en, yue, ar, de, fr, es, pt, id, it, ko, ru, th, vi, ja, tr, hi, ms, nl, sv, da, fi, pl, cs, tl, fa, el, hu, mk, ro.
| Model | Size (GB) | Speed Factor | Private Leaderboard Rank† | Word Error Rate† |
|---|---|---|---|---|
| Qwen3-ASR | 1.8 | 10x | 1st | 8.1 |
| Parakeet v2/v3 | 0.5 | 300x | 22nd | 9.2 |
| Whisper Large v3 Turbo | 0.6 | 20x | 43rd | 10.2 |
† Real-world English conversational as well as scripted data with accent diversity. New private data benchmarks rule out leaderboard hacking.
Qwen3-ASR has a total of ~2 billion parameters and requires 1.8 GB for download and storage: 4x the size of Parakeet v2 and 3x the size of Whisper Large v3 Turbo. It transcribes 10 seconds of audio per wall-clock second, roughly 30x slower than the Parakeet models and 2x slower than Whisper Large v3 Turbo.
We recommend this model only when accuracy is the overriding constraint and your application can budget for that storage and throughput. For most applications, the Parakeet models above remain the most suitable model.
Use the qwen3ASR configuration factory rather than naming the model directly. It points at the argmaxinc/qwenasrkit-pro repository and fills in the Qwen defaults:
let whisperKit = try await WhisperKitPro(.qwen3ASR())Platform Support. Please see Supported Platforms - Qwen3-ASR to fence deployment for a reliable experience.
Memory and Speed Trade-off
ModelOptimizationMode trades off speed for peak memory without impacting output:
.latencyOptimized: Fastest results and highest peak memory usage..memoryOptimized: Approximately 1 GB reduction in peak memory at a variable latency cost..auto(default):.latencyOptimizedon devices with 8 GB of memory or more,.memoryOptimizedbelow that.
let whisperKit = try await WhisperKitPro(.qwen3ASR(optimization: .memoryOptimized))
// Which profile `.auto` works out to on this device. Reporting only.
let effective = ModelOptimizationMode.auto.resolvedOpenAI Whisper Large v3 Turbo
The original models for WhisperKit (now called Argmax OSS) are hosted under the .openSourceRepo repository.
Argmax Pro SDK hosts a second set of Whisper models under the .proRepo repository that are further optimized for speed and energy-efficiency compared to their .openSourceRepo counterparts. During this upgrade, accuracy remains identical while speed and energy-efficiency improve significantly.
Usage:
let config = WhisperKitProConfig(
model: "large-v3-v20240930_626MB",
modelRepo: .proRepo // or .openSourceRepo
)OS Compatibility. Note that .proRepo models support iOS 18/macOS 15 and
newer. For users still on iOS 17/macOS 14, please fall back to
.openSourceRepo counterparts.
Nvidia Parakeet models are hosted under .parakeetRepo (repository).
Downloading the Model
Initialize Argmax SDK
Argmax SDK requires initialization with an Argmax API key (starts with ax_***, not axst_***) to unlock Pro models and features.
We recommend fetching your API key securely from your backend in production. However, we provide a simple obfuscator to protect against casual inspection and static analysis tools.
Assuming you use ObfuscatedKeyProvider.generateCodeExample to obfuscate your API key, you may initialize Argmax SDK as follows:
var keyProvider = ObfuscatedKeyProvider(mask: 37) // placeholder values
keyProvider.apiKeyObfuscated = [4, 5, 6] // placeholder values
guard let apiKey = keyProvider.apiKey else {
fatalError("Missing API key")
}
await ArgmaxSDK.with(ArgmaxConfig(apiKey: apiKey))Note that ArgmaxSDK.with(ArgmaxConfig(apiKey: apiKey)) requires an internet connection during first use and at least once every 30 days to maintain an active license. See this documentation page to learn more.
Initiate Download
ModelStore implements a robust model downloader. On iOS and iPadOS, this model downloader persists progress across foreground-to-background and background-to-foreground app transitions. It evens persists the download progress after the app is killed.
This downloader is designed to enable your application to set up Argmax in the background without blocking the user on a download spinner.
Forward background URLSession events from your UIApplicationDelegate so the SDK can drain them before iOS re-suspends the app:
func application(
_ application: UIApplication,
handleEventsForBackgroundURLSession identifier: String,
completionHandler: @escaping () -> Void
) {
modelStore.handleEventsForBackgroundSession(
identifier: identifier,
completionHandler: completionHandler
)
}For SwiftUI apps without an AppDelegate, wire one in with @UIApplicationDelegateAdaptor.
import Network // for NWInterface.InterfaceType
let modelStore = ModelStore(config: config) // `config` from "Picking the Model"
do {
let result = try await modelStore.downloadModelInBackground(
name: "large-v3-v20240930_626MB",
repo: RepoType.proRepo,
disabledNetworkTypes: [.cellular] // optional: Wi-Fi only
)
switch result {
case .started(let downloadId):
print("Download started: \(downloadId)")
case .resumed(let downloadId):
print("Resuming existing download: \(downloadId)")
case .alreadyInProgress(let downloadId):
print("Download already running: \(downloadId)")
case .waitingForNetwork(let downloadId):
print("Queued, will resume when Wi-Fi is available: \(downloadId)")
case .alreadyComplete(let modelPath):
print("Model already downloaded at: \(modelPath)")
}
} catch {
print("Failed to start download: \(error)")
}Query State
modelStore.getBackgroundDownloadState returns the following state:
public struct BackgroundDownloadState {
let downloadId: String
let modelVariant: String
let repoId: String
var files: [BackgroundFileDownload]
var status: BackgroundDownloadStatus // .pending, .downloading, .paused, .completed, .failed
let startedAt: Date
var completedAt: Date?
var overallProgress: Double // 0.0 to 1.0
}Here is a simple way to query download state:
// Get all active downloads
let downloads = modelStore.activeBackgroundDownloads
// Get state for first active download
if let download = downloads.first {
print("Progress: \(download.overallProgress)")
print("Files completed: \(download.completedFileCount)/\(download.totalFileCount)")
}For reactive updates, AsyncStream is the preferred API for new code. Each subscriber receives the current snapshot immediately, then every subsequent change:
Task {
for await downloads in modelStore.activeBackgroundDownloadUpdates {
for download in downloads {
let pct = Int(download.overallProgress * 100)
print("\(download.modelVariant): \(pct)% status=\(download.status.rawValue)")
}
}
}A Combine projection (modelStore.backgroundDownloadsPublisher) is also available for back-compat:
import Combine
var cancellables = Set<AnyCancellable>()
modelStore.backgroundDownloadsPublisher
.receive(on: DispatchQueue.main)
.sink { downloads in
for download in downloads {
print("\(download.modelVariant): \(Int(download.overallProgress * 100))%")
print("Status: \(download.status)")
// Individual file progress
for file in download.files {
print(" \(file.destinationURL.lastPathComponent): \(file.status)")
}
}
}
.store(in: &cancellables)Pause and Resume
modelStore.pauseBackgroundDownload(downloadId)do {
try await modelStore.resumeBackgroundDownload(downloadId)
} catch {
print("Failed to resume: \(error)")
}Cancel
modelStore.cancelBackgroundDownload(downloadId, deleteProgress: false)Network Interface Restrictions
disabledNetworkTypes accepts a list of NWInterface.InterfaceType values that the download is not allowed to use. The SDK auto-pauses when the active path uses a disabled interface and auto-resumes when an allowed one returns. The restriction is persisted with the download record and survives app relaunches.
Change a restriction at any time without disturbing the in-flight transfer:
// Lift the restriction (e.g. user tapped "Allow on cellular"):
modelStore.setDisabledBackgroundDownloadNetworkTypes(nil, for: downloadId)
// Add a restriction mid-flight:
modelStore.setDisabledBackgroundDownloadNetworkTypes([.cellular], for: downloadId)Crash Recovery
Downloads persisted as in-flight but with no live URLSession tasks on relaunch (the signature of an abnormal exit such as a crash, force-quit, or device reboot) auto-resume after ModelStore initializes. No manual user action is required.
Downloads explicitly paused by the user stay paused across relaunches; only crash-paused downloads auto-resume.
Loading the Model
After downloading, call loadModels() to load the MelSpectrogram, AudioEncoder, and TextDecoder models from the model folder into memory. The first load after a download may take 15–90 seconds because CoreML compiles the models on-device. Subsequent loads are near-instant thanks to the OS-level compiled model cache.
let whisperKitPro = try await WhisperKitPro(config) // same config used during "Downloading the Model"
// loadModels() is called automatically during WhisperKitPro initialization.
// You can also call it explicitly if you initialized with `load: false`:
try await whisperKitPro.loadModels()loadModels() supports a prewarmMode parameter. When prewarmMode: true, models are loaded but not fully initialized, allowing you to defer the final initialization to a later point.
Warming Up the Model
Once the model is compiled during first use, Apple caches the compiled model in an OS-level cache, not accessible by any third-party including Argmax. This enables subsequent model loads to be near-instant due to compiled model cache hits. However, Apple evicts this cache after each OS update, when running low on free disk space or after extended periods of non-use (~14 days). Apple does not expose an API for checking the cache state and developers can not reliably predict cache hit or miss. This leads to occassional surprise recompilation latency during non-first use.
ModelWarmup mitigates this surprise by periodically attempting to recompile actively used models on a system-scheduled cadence in the background, keeping load latency under a second after first-time use. ModelWarmup never downloads models, never runs inference, and never makes licensing or model network requests.
macOS
Step 1. Bundle a launch agent plist named <bundle identifier>.modelwarmup.plist and copy it into Contents/Library/LaunchAgents/ with a build phase:
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.example.MyApp.modelwarmup</string>
<key>BundleProgram</key>
<string>Contents/MacOS/MyApp</string>
<key>ProgramArguments</key>
<array>
<string>MyApp</string>
<string>--argmax-warm-only</string>
</array>
<key>StartInterval</key>
<integer>3600</integer>
</dict>
</plist>Label matches the filename minus .plist. BundleProgram must be your app's own executable, since a helper binary would warm a compile cache your app never reads. ProgramArguments must pass --argmax-warm-only, otherwise every heartbeat opens the full app.
Step 2. Handle the warm-only launch first thing in main(), before any UI exists:
@main
enum Entry {
static func main() async {
await ModelWarmup.handleWarmOnlyLaunchIfNeeded()
MyApp.main()
}
}
struct MyApp: App {
init() { ModelWarmup.register() }
var body: some Scene { WindowGroup { ContentView() } }
}ModelWarmup.register() auto-enables the agent on a .daily cadence. Call enableBackgroundAgent(schedule:) only to change that cadence, or to re-enable after disableBackgroundAgent().
The user can veto the agent. Registering adds your app to System Settings > General > Login Items & Extensions, where macOS notifies the user and lets them disable it. backgroundAgentStatus() returns .requiresApproval in that case, and only the user can re-enable it. Send them there with openLoginItemsSettings(). The agent runs only while the user is logged in and the Mac is awake.
iOS
Step 1. Enable the Background processing background mode under Signing & Capabilities.
Step 2. Allowlist the task identifier in Info.plist:
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array><string>com.argmaxinc.sdk.modelwarmup</string></array>
<key>UIBackgroundModes</key>
<array><string>processing</string></array>Step 3. Call ModelWarmup.register() from App.init or didFinishLaunchingWithOptions.
iOS registration must be synchronous. BGTaskScheduler raises an
uncatchable exception when handler registration happens after
didFinishLaunchingWithOptions returns, so register() must complete during
app launch. macOS has no such constraint. register() returns false with an
actionable log message when setup cannot succeed, such as a missing
Info.plist entry.
Scheduling and Targets
Background runs default to a daily charging-time cadence. setSchedule(_:) accepts .daily, .immediateOnce, or .repeating(interval:requiresCharging:) for apps with their own cadence needs. The schedule is not persisted, so every launch starts on .daily and a custom schedule must be set after register().
Warmup automatically selects the most recently used Argmax models across all Kits. Override that with setWarmTargets(_:), read the override back with warmTargets(), and restore automatic selection with clearWarmTargets(). warmTargets() returns nil while selection is automatic, which keeps an empty override (setWarmTargets([]), selecting nothing) distinguishable from no override at all.
Observability
warmNow()warms in the foreground. Use it for integration tests, or right after a download completes. Do not call it during active inference.history()reports past runs newest first, with per-model outcomes, whether the cache was actually cold, skip reasons, and run-over-run environment deltas such as OS or SDK changes. History is bounded to 30 runs and 90 days.nextScheduledWarmup()reports the earliest date the OS may run the pending warmup. It is authoritative on iOS and an estimate on macOS, which exposes no fire date.clearHistory()removes all persisted warmup state, which stays under 100 KB in Application Support.
disable() opts out for the current launch. On macOS a durable opt-out must also call disableBackgroundAgent(), because a registered launch agent keeps warming with the app closed.
Bring Your Own Model
Argmax Pro SDK supports downloading model files directly from a custom HTTPS URL such as S3 presigned URLs, GCP signed URLs, Azure SAS URLs or your own CDN.
The model folder must be archived as an Apple Archive (.aar). The model folder should preserve the directory structure of the corresponding Argmax ready-made Hugging Face-hosted model. For example, the .aar hosted at https://models.example.com/whisperkit-coreml/openai_whisper-tiny.aar should carry this directory structure.
Create the archive on macOS with the built-in aa (Apple Archive) tool. Point -d at the base directory that holds the argmaxinc/ namespace and pass the full model path to -subdir so the archive preserves the Hugging Face directory structure:
# Directory layout under BASE_DIR before archiving:
# argmaxinc/
# whisperkit-coreml/
# openai_whisper-tiny/
# config.json
# *.mlmodelc (AudioEncoder, TextDecoder, MelSpectrogram, ...)
# LICENSE_NOTICE.txt
# tokenizer files (optional, Whisper only: tokenizer.json, tokenizer_config.json)
aa archive \
-d <BASE_DIR> \
-subdir argmaxinc/whisperkit-coreml/openai_whisper-tiny \
-o openai_whisper-tiny.aar-subdir keeps the full model path as the top-level structure inside the archive, which is what the SDK's extractor walks to locate the variant folder. Include the complete contents of the model directory, mirroring the Hugging Face layout (each model folder should carry its own LICENSE_NOTICE.txt). aa uses LZFSE compression by default, and the SDK auto-detects the algorithm on extract. Upload the resulting .aar to your storage, then hand the SDK a URL to it:
let archiveURL = URL(string:
"https://models.example.com/whisperkit-coreml/openai_whisper-tiny.aar?X-Amz-Signature=..."
)!
// One-step: download + extract + register with the model cache.
let modelFolder = try await modelStore.downloadAndExtractInBackgroundAndWait(
remoteURL: archiveURL,
destinationRoot: URL.documentsDirectory.appendingPathComponent("Models"),
disabledNetworkTypes: [.cellular] // optional: Wi-Fi only
)
let config = WhisperKitProConfig(modelFolder: modelFolder.path)
let whisperKit = try await WhisperKitPro(config)- The download request does not carry an
Authorizationheader. URLs that carry their signature in the query string work without further configuration. - Repeated calls with a rotated pre-signed URL for the same object return
.alreadyCompleteinstead of re-downloading. Pass explicitmodelVariant:/repoId:values if you want to control the cache key instead of deriving it from the URL path. - If download or extract fails for the.aarfile, partial output is cleaned up so the next attempt starts from a known-empty folder. - The.aarfile is removed after successful extraction. - The final extracted local folder is registered with the download cache so loading models do not attempt to redownload from the configured Hugging Face repo. - For Whisper models, bundle the tokenizer files inside the model folder to load fully offline:tokenizer.json(required; this is the file the SDK checks for) andtokenizer_config.json. Download both from the matchingopenai/whisper-*repository on Hugging Face, e.g. openai/whisper-tiny foropenai_whisper-tiny.WhisperKitProsearches themodelFolderfor a bundled tokenizer before falling back to a network fetch, so no separatetokenizerFolderis needed. Parakeet models need no tokenizer files; their tokenizers ship inside the SDK. - For full control, e.g. pause/resume, mid-flight network-type changes, progress observation, use the non-waitingmodelStore.downloadAndExtractInBackground(remoteURL:...), which returns aBackgroundDownloadResultwith adownloadId, and follow the patterns covered in Network Interface Restrictions.