jhenhong commited on
Commit
5767578
·
unverified ·
1 Parent(s): 3c4a8bb

whisper.swiftui : add model download list & bench methods (#2546)

Browse files

* swift : fix resources & exclude build

* whisper : impl whisper_timings struct & api

* whisper.swiftui : model list & bench methods

* whisper : return ptr for whisper_get_timings

* revert unnecessary change

* whisper : avoid designated initializer

* whisper.swiftui: code style changes

* whisper.swiftui : get device name / os from UIDevice

* whisper.swiftui : fix UIDevice usage

* whisper.swiftui : add memcpy and ggml_mul_mat (commented)

Package.swift CHANGED
@@ -18,16 +18,17 @@ let package = Package(
18
  name: "whisper",
19
  path: ".",
20
  exclude: [
 
21
  "bindings",
22
  "cmake",
23
- "coreml",
24
  "examples",
25
- "extra",
26
  "models",
27
  "samples",
28
  "tests",
29
  "CMakeLists.txt",
30
- "Makefile"
 
31
  ],
32
  sources: [
33
  "ggml/src/ggml.c",
@@ -38,7 +39,7 @@ let package = Package(
38
  "ggml/src/ggml-quants.c",
39
  "ggml/src/ggml-metal.m"
40
  ],
41
- resources: [.process("ggml-metal.metal")],
42
  publicHeadersPath: "spm-headers",
43
  cSettings: [
44
  .unsafeFlags(["-Wno-shorten-64-to-32", "-O3", "-DNDEBUG"]),
 
18
  name: "whisper",
19
  path: ".",
20
  exclude: [
21
+ "build",
22
  "bindings",
23
  "cmake",
 
24
  "examples",
25
+ "scripts",
26
  "models",
27
  "samples",
28
  "tests",
29
  "CMakeLists.txt",
30
+ "Makefile",
31
+ "ggml/src/ggml-metal-embed.metal"
32
  ],
33
  sources: [
34
  "ggml/src/ggml.c",
 
39
  "ggml/src/ggml-quants.c",
40
  "ggml/src/ggml-metal.m"
41
  ],
42
+ resources: [.process("ggml/src/ggml-metal.metal")],
43
  publicHeadersPath: "spm-headers",
44
  cSettings: [
45
  .unsafeFlags(["-Wno-shorten-64-to-32", "-O3", "-DNDEBUG"]),
examples/whisper.swiftui/whisper.cpp.swift/LibWhisper.swift CHANGED
@@ -1,4 +1,5 @@
1
  import Foundation
 
2
  import whisper
3
 
4
  enum WhisperError: Error {
@@ -55,11 +56,93 @@ actor WhisperContext {
55
  return transcription
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  static func createContext(path: String) throws -> WhisperContext {
59
  var params = whisper_context_default_params()
60
  #if targetEnvironment(simulator)
61
  params.use_gpu = false
62
  print("Running on the simulator, using CPU")
 
 
63
  #endif
64
  let context = whisper_init_from_file_with_params(path, params)
65
  if let context {
 
1
  import Foundation
2
+ import UIKit
3
  import whisper
4
 
5
  enum WhisperError: Error {
 
56
  return transcription
57
  }
58
 
59
+ static func benchMemcpy(nThreads: Int32) async -> String {
60
+ return String.init(cString: whisper_bench_memcpy_str(nThreads))
61
+ }
62
+
63
+ static func benchGgmlMulMat(nThreads: Int32) async -> String {
64
+ return String.init(cString: whisper_bench_ggml_mul_mat_str(nThreads))
65
+ }
66
+
67
+ private func systemInfo() -> String {
68
+ var info = ""
69
+ if (ggml_cpu_has_neon() != 0) { info += "NEON " }
70
+ if (ggml_cpu_has_metal() != 0) { info += "METAL " }
71
+ if (ggml_cpu_has_blas() != 0) { info += "BLAS " }
72
+ return String(info.dropLast())
73
+ }
74
+
75
+ func benchFull(modelName: String, nThreads: Int32) async -> String {
76
+ let nMels = whisper_model_n_mels(context)
77
+ if (whisper_set_mel(context, nil, 0, nMels) != 0) {
78
+ return "error: failed to set mel"
79
+ }
80
+
81
+ // heat encoder
82
+ if (whisper_encode(context, 0, nThreads) != 0) {
83
+ return "error: failed to encode"
84
+ }
85
+
86
+ var tokens = [whisper_token](repeating: 0, count: 512)
87
+
88
+ // prompt heat
89
+ if (whisper_decode(context, &tokens, 256, 0, nThreads) != 0) {
90
+ return "error: failed to decode"
91
+ }
92
+
93
+ // text-generation heat
94
+ if (whisper_decode(context, &tokens, 1, 256, nThreads) != 0) {
95
+ return "error: failed to decode"
96
+ }
97
+
98
+ whisper_reset_timings(context)
99
+
100
+ // actual run
101
+ if (whisper_encode(context, 0, nThreads) != 0) {
102
+ return "error: failed to encode"
103
+ }
104
+
105
+ // text-generation
106
+ for i in 0..<256 {
107
+ if (whisper_decode(context, &tokens, 1, Int32(i), nThreads) != 0) {
108
+ return "error: failed to decode"
109
+ }
110
+ }
111
+
112
+ // batched decoding
113
+ for _ in 0..<64 {
114
+ if (whisper_decode(context, &tokens, 5, 0, nThreads) != 0) {
115
+ return "error: failed to decode"
116
+ }
117
+ }
118
+
119
+ // prompt processing
120
+ for _ in 0..<16 {
121
+ if (whisper_decode(context, &tokens, 256, 0, nThreads) != 0) {
122
+ return "error: failed to decode"
123
+ }
124
+ }
125
+
126
+ whisper_print_timings(context)
127
+
128
+ let deviceModel = await UIDevice.current.model
129
+ let systemName = await UIDevice.current.systemName
130
+ let systemInfo = self.systemInfo()
131
+ let timings: whisper_timings = whisper_get_timings(context).pointee
132
+ let encodeMs = String(format: "%.2f", timings.encode_ms)
133
+ let decodeMs = String(format: "%.2f", timings.decode_ms)
134
+ let batchdMs = String(format: "%.2f", timings.batchd_ms)
135
+ let promptMs = String(format: "%.2f", timings.prompt_ms)
136
+ return "| \(deviceModel) | \(systemName) | \(systemInfo) | \(modelName) | \(nThreads) | 1 | \(encodeMs) | \(decodeMs) | \(batchdMs) | \(promptMs) | <todo> |"
137
+ }
138
+
139
  static func createContext(path: String) throws -> WhisperContext {
140
  var params = whisper_context_default_params()
141
  #if targetEnvironment(simulator)
142
  params.use_gpu = false
143
  print("Running on the simulator, using CPU")
144
+ #else
145
+ params.flash_attn = true // Enabled by default for Metal
146
  #endif
147
  let context = whisper_init_from_file_with_params(path, params)
148
  if let context {
examples/whisper.swiftui/whisper.swiftui.demo/Models/Model.swift ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Foundation
2
+
3
+ struct Model: Identifiable {
4
+ var id = UUID()
5
+ var name: String
6
+ var info: String
7
+ var url: String
8
+
9
+ var filename: String
10
+ var fileURL: URL {
11
+ FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0].appendingPathComponent(filename)
12
+ }
13
+
14
+ func fileExists() -> Bool {
15
+ FileManager.default.fileExists(atPath: fileURL.path)
16
+ }
17
+ }
examples/whisper.swiftui/whisper.swiftui.demo/Models/WhisperState.swift CHANGED
@@ -14,7 +14,7 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
14
  private var recordedFile: URL? = nil
15
  private var audioPlayer: AVAudioPlayer?
16
 
17
- private var modelUrl: URL? {
18
  Bundle.main.url(forResource: "ggml-base.en", withExtension: "bin", subdirectory: "models")
19
  }
20
 
@@ -28,23 +28,59 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
28
 
29
  override init() {
30
  super.init()
 
 
 
 
31
  do {
32
- try loadModel()
 
 
 
 
 
 
 
 
33
  canTranscribe = true
34
  } catch {
35
  print(error.localizedDescription)
36
- messageLog += "\(error.localizedDescription)\n"
37
  }
38
  }
39
-
40
- private func loadModel() throws {
41
- messageLog += "Loading model...\n"
42
- if let modelUrl {
43
- whisperContext = try WhisperContext.createContext(path: modelUrl.path())
44
- messageLog += "Loaded model \(modelUrl.lastPathComponent)\n"
45
- } else {
46
- messageLog += "Could not locate model\n"
47
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
  func transcribeSample() async {
@@ -160,3 +196,8 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
160
  isRecording = false
161
  }
162
  }
 
 
 
 
 
 
14
  private var recordedFile: URL? = nil
15
  private var audioPlayer: AVAudioPlayer?
16
 
17
+ private var builtInModelUrl: URL? {
18
  Bundle.main.url(forResource: "ggml-base.en", withExtension: "bin", subdirectory: "models")
19
  }
20
 
 
28
 
29
  override init() {
30
  super.init()
31
+ loadModel()
32
+ }
33
+
34
+ func loadModel(path: URL? = nil, log: Bool = true) {
35
  do {
36
+ whisperContext = nil
37
+ if (log) { messageLog += "Loading model...\n" }
38
+ let modelUrl = path ?? builtInModelUrl
39
+ if let modelUrl {
40
+ whisperContext = try WhisperContext.createContext(path: modelUrl.path())
41
+ if (log) { messageLog += "Loaded model \(modelUrl.lastPathComponent)\n" }
42
+ } else {
43
+ if (log) { messageLog += "Could not locate model\n" }
44
+ }
45
  canTranscribe = true
46
  } catch {
47
  print(error.localizedDescription)
48
+ if (log) { messageLog += "\(error.localizedDescription)\n" }
49
  }
50
  }
51
+
52
+ func benchCurrentModel() async {
53
+ if whisperContext == nil {
54
+ messageLog += "Cannot bench without loaded model\n"
55
+ return
 
 
 
56
  }
57
+ messageLog += "Running benchmark for loaded model\n"
58
+ let result = await whisperContext?.benchFull(modelName: "<current>", nThreads: Int32(min(4, cpuCount())))
59
+ if (result != nil) { messageLog += result! + "\n" }
60
+ }
61
+
62
+ func bench(models: [Model]) async {
63
+ let nThreads = Int32(min(4, cpuCount()))
64
+
65
+ // messageLog += "Running memcpy benchmark\n"
66
+ // messageLog += await WhisperContext.benchMemcpy(nThreads: nThreads) + "\n"
67
+ //
68
+ // messageLog += "Running ggml_mul_mat benchmark with \(nThreads) threads\n"
69
+ // messageLog += await WhisperContext.benchGgmlMulMat(nThreads: nThreads) + "\n"
70
+
71
+ messageLog += "Running benchmark for all downloaded models\n"
72
+ messageLog += "| CPU | OS | Config | Model | Th | FA | Enc. | Dec. | Bch5 | PP | Commit |\n"
73
+ messageLog += "| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n"
74
+ for model in models {
75
+ loadModel(path: model.fileURL, log: false)
76
+ if whisperContext == nil {
77
+ messageLog += "Cannot bench without loaded model\n"
78
+ break
79
+ }
80
+ let result = await whisperContext?.benchFull(modelName: model.name, nThreads: nThreads)
81
+ if (result != nil) { messageLog += result! + "\n" }
82
+ }
83
+ messageLog += "Benchmarking completed\n"
84
  }
85
 
86
  func transcribeSample() async {
 
196
  isRecording = false
197
  }
198
  }
199
+
200
+
201
+ fileprivate func cpuCount() -> Int {
202
+ ProcessInfo.processInfo.processorCount
203
+ }
examples/whisper.swiftui/whisper.swiftui.demo/UI/ContentView.swift CHANGED
@@ -1,5 +1,6 @@
1
  import SwiftUI
2
  import AVFoundation
 
3
 
4
  struct ContentView: View {
5
  @StateObject var whisperState = WhisperState()
@@ -29,15 +30,125 @@ struct ContentView: View {
29
  Text(verbatim: whisperState.messageLog)
30
  .frame(maxWidth: .infinity, alignment: .leading)
31
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
  .navigationTitle("Whisper SwiftUI Demo")
34
  .padding()
35
  }
36
  }
37
- }
38
 
39
- struct ContentView_Previews: PreviewProvider {
40
- static var previews: some View {
41
- ContentView()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
  }
 
 
 
 
 
 
 
1
  import SwiftUI
2
  import AVFoundation
3
+ import Foundation
4
 
5
  struct ContentView: View {
6
  @StateObject var whisperState = WhisperState()
 
30
  Text(verbatim: whisperState.messageLog)
31
  .frame(maxWidth: .infinity, alignment: .leading)
32
  }
33
+ .font(.footnote)
34
+ .padding()
35
+ .background(Color.gray.opacity(0.1))
36
+ .cornerRadius(10)
37
+
38
+ HStack {
39
+ Button("Clear Logs", action: {
40
+ whisperState.messageLog = ""
41
+ })
42
+ .font(.footnote)
43
+ .buttonStyle(.bordered)
44
+
45
+ Button("Copy Logs", action: {
46
+ UIPasteboard.general.string = whisperState.messageLog
47
+ })
48
+ .font(.footnote)
49
+ .buttonStyle(.bordered)
50
+
51
+ Button("Bench", action: {
52
+ Task {
53
+ await whisperState.benchCurrentModel()
54
+ }
55
+ })
56
+ .font(.footnote)
57
+ .buttonStyle(.bordered)
58
+ .disabled(!whisperState.canTranscribe)
59
+
60
+ Button("Bench All", action: {
61
+ Task {
62
+ await whisperState.bench(models: ModelsView.getDownloadedModels())
63
+ }
64
+ })
65
+ .font(.footnote)
66
+ .buttonStyle(.bordered)
67
+ .disabled(!whisperState.canTranscribe)
68
+ }
69
+
70
+ NavigationLink(destination: ModelsView(whisperState: whisperState)) {
71
+ Text("View Models")
72
+ }
73
+ .font(.footnote)
74
+ .padding()
75
  }
76
  .navigationTitle("Whisper SwiftUI Demo")
77
  .padding()
78
  }
79
  }
 
80
 
81
+ struct ModelsView: View {
82
+ @ObservedObject var whisperState: WhisperState
83
+ @Environment(\.dismiss) var dismiss
84
+
85
+ private static let models: [Model] = [
86
+ Model(name: "tiny", info: "(F16, 75 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin", filename: "tiny.bin"),
87
+ Model(name: "tiny-q5_1", info: "(31 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.bin", filename: "tiny-q5_1.bin"),
88
+ Model(name: "tiny-q8_0", info: "(42 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q8_0.bin", filename: "tiny-q8_0.bin"),
89
+ Model(name: "tiny.en", info: "(F16, 75 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en.bin", filename: "tiny.en.bin"),
90
+ Model(name: "tiny.en-q5_1", info: "(31 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q5_1.bin", filename: "tiny.en-q5_1.bin"),
91
+ Model(name: "tiny.en-q8_0", info: "(42 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.en-q8_0.bin", filename: "tiny.en-q8_0.bin"),
92
+ Model(name: "base", info: "(F16, 142 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin", filename: "base.bin"),
93
+ Model(name: "base-q5_1", info: "(57 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q5_1.bin", filename: "base-q5_1.bin"),
94
+ Model(name: "base-q8_0", info: "(78 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base-q8_0.bin", filename: "base-q8_0.bin"),
95
+ Model(name: "base.en", info: "(F16, 142 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin", filename: "base.en.bin"),
96
+ Model(name: "base.en-q5_1", info: "(57 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q5_1.bin", filename: "base.en-q5_1.bin"),
97
+ Model(name: "base.en-q8_0", info: "(78 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en-q8_0.bin", filename: "base.en-q8_0.bin"),
98
+ Model(name: "small", info: "(F16, 466 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin", filename: "small.bin"),
99
+ Model(name: "small-q5_1", info: "(181 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q5_1.bin", filename: "small-q5_1.bin"),
100
+ Model(name: "small-q8_0", info: "(252 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small-q8_0.bin", filename: "small-q8_0.bin"),
101
+ Model(name: "small.en", info: "(F16, 466 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en.bin", filename: "small.en.bin"),
102
+ Model(name: "small.en-q5_1", info: "(181 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en-q5_1.bin", filename: "small.en-q5_1.bin"),
103
+ Model(name: "small.en-q8_0", info: "(252 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.en-q8_0.bin", filename: "small.en-q8_0.bin"),
104
+ Model(name: "medium", info: "(F16, 1.5 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin", filename: "medium.bin"),
105
+ Model(name: "medium-q5_0", info: "(514 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q5_0.bin", filename: "medium-q5_0.bin"),
106
+ Model(name: "medium-q8_0", info: "(785 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium-q8_0.bin", filename: "medium-q8_0.bin"),
107
+ Model(name: "medium.en", info: "(F16, 1.5 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en.bin", filename: "medium.en.bin"),
108
+ Model(name: "medium.en-q5_0", info: "(514 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en-q5_0.bin", filename: "medium.en-q5_0.bin"),
109
+ Model(name: "medium.en-q8_0", info: "(785 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.en-q8_0.bin", filename: "medium.en-q8_0.bin"),
110
+ Model(name: "large-v1", info: "(F16, 2.9 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large.bin", filename: "large.bin"),
111
+ Model(name: "large-v2", info: "(F16, 2.9 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v2.bin", filename: "large-v2.bin"),
112
+ Model(name: "large-v2-q5_0", info: "(1.1 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v2-q5_0.bin", filename: "large-v2-q5_0.bin"),
113
+ Model(name: "large-v2-q8_0", info: "(1.5 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v2-q8_0.bin", filename: "large-v2-q8_0.bin"),
114
+ Model(name: "large-v3", info: "(F16, 2.9 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin", filename: "large-v3.bin"),
115
+ Model(name: "large-v3-q5_0", info: "(1.1 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-q5_0.bin", filename: "large-v3-q5_0.bin"),
116
+ Model(name: "large-v3-turbo", info: "(F16, 1.5 GiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo.bin", filename: "large-v3-turbo.bin"),
117
+ Model(name: "large-v3-turbo-q5_0", info: "(547 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q5_0.bin", filename: "large-v3-turbo-q5_0.bin"),
118
+ Model(name: "large-v3-turbo-q8_0", info: "(834 MiB)", url: "https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3-turbo-q8_0.bin", filename: "large-v3-turbo-q8_0.bin"),
119
+ ]
120
+
121
+ static func getDownloadedModels() -> [Model] {
122
+ // Filter models that have been downloaded
123
+ return models.filter {
124
+ FileManager.default.fileExists(atPath: $0.fileURL.path())
125
+ }
126
+ }
127
+
128
+ func loadModel(model: Model) {
129
+ Task {
130
+ dismiss()
131
+ whisperState.loadModel(path: model.fileURL)
132
+ }
133
+ }
134
+
135
+ var body: some View {
136
+ List {
137
+ Section(header: Text("Models")) {
138
+ ForEach(ModelsView.models) { model in
139
+ DownloadButton(model: model)
140
+ .onLoad(perform: loadModel)
141
+ }
142
+ }
143
+ }
144
+ .listStyle(GroupedListStyle())
145
+ .navigationBarTitle("Models", displayMode: .inline).toolbar {}
146
+ }
147
  }
148
  }
149
+
150
+ //struct ContentView_Previews: PreviewProvider {
151
+ // static var previews: some View {
152
+ // ContentView()
153
+ // }
154
+ //}
examples/whisper.swiftui/whisper.swiftui.demo/UI/DownloadButton.swift ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import SwiftUI
2
+
3
+ struct DownloadButton: View {
4
+ private var model: Model
5
+
6
+ @State private var status: String
7
+
8
+ @State private var downloadTask: URLSessionDownloadTask?
9
+ @State private var progress = 0.0
10
+ @State private var observation: NSKeyValueObservation?
11
+
12
+ private var onLoad: ((_ model: Model) -> Void)?
13
+
14
+ init(model: Model) {
15
+ self.model = model
16
+ status = model.fileExists() ? "downloaded" : "download"
17
+ }
18
+
19
+ func onLoad(perform action: @escaping (_ model: Model) -> Void) -> DownloadButton {
20
+ var button = self
21
+ button.onLoad = action
22
+ return button
23
+ }
24
+
25
+ private func download() {
26
+ status = "downloading"
27
+ print("Downloading model \(model.name) from \(model.url)")
28
+ guard let url = URL(string: model.url) else { return }
29
+
30
+ downloadTask = URLSession.shared.downloadTask(with: url) { temporaryURL, response, error in
31
+ if let error = error {
32
+ print("Error: \(error.localizedDescription)")
33
+ return
34
+ }
35
+
36
+ guard let response = response as? HTTPURLResponse, (200...299).contains(response.statusCode) else {
37
+ print("Server error!")
38
+ return
39
+ }
40
+
41
+ do {
42
+ if let temporaryURL = temporaryURL {
43
+ try FileManager.default.copyItem(at: temporaryURL, to: model.fileURL)
44
+ print("Writing to \(model.filename) completed")
45
+ status = "downloaded"
46
+ }
47
+ } catch let err {
48
+ print("Error: \(err.localizedDescription)")
49
+ }
50
+ }
51
+
52
+ observation = downloadTask?.progress.observe(\.fractionCompleted) { progress, _ in
53
+ self.progress = progress.fractionCompleted
54
+ }
55
+
56
+ downloadTask?.resume()
57
+ }
58
+
59
+ var body: some View {
60
+ VStack {
61
+ Button(action: {
62
+ if (status == "download") {
63
+ download()
64
+ } else if (status == "downloading") {
65
+ downloadTask?.cancel()
66
+ status = "download"
67
+ } else if (status == "downloaded") {
68
+ if !model.fileExists() {
69
+ download()
70
+ }
71
+ onLoad?(model)
72
+ }
73
+ }) {
74
+ let title = "\(model.name) \(model.info)"
75
+ if (status == "download") {
76
+ Text("Download \(title)")
77
+ } else if (status == "downloading") {
78
+ Text("\(title) (Downloading \(Int(progress * 100))%)")
79
+ } else if (status == "downloaded") {
80
+ Text("Load \(title)")
81
+ } else {
82
+ Text("Unknown status")
83
+ }
84
+ }.swipeActions {
85
+ if (status == "downloaded") {
86
+ Button("Delete") {
87
+ do {
88
+ try FileManager.default.removeItem(at: model.fileURL)
89
+ } catch {
90
+ print("Error deleting file: \(error)")
91
+ }
92
+ status = "download"
93
+ }
94
+ .tint(.red)
95
+ }
96
+ }
97
+ }
98
+ .onDisappear() {
99
+ downloadTask?.cancel()
100
+ }
101
+ }
102
+ }
examples/whisper.swiftui/whisper.swiftui.xcodeproj/project.pbxproj CHANGED
@@ -17,6 +17,8 @@
17
  0AAC5D9F29539CD0003032C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AAC5D9E29539CD0003032C3 /* Assets.xcassets */; };
18
  0AAC5DCE2953A05C003032C3 /* WhisperState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AAC5DCD2953A05C003032C3 /* WhisperState.swift */; };
19
  0AAC5DD12953A394003032C3 /* LibWhisper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AAC5DD02953A394003032C3 /* LibWhisper.swift */; };
 
 
20
  E3F92DC52AFA8E3800A6A9D4 /* whisper in Frameworks */ = {isa = PBXBuildFile; productRef = E3F92DC42AFA8E3800A6A9D4 /* whisper */; };
21
  /* End PBXBuildFile section */
22
 
@@ -33,6 +35,8 @@
33
  0AAC5DA029539CD0003032C3 /* WhisperCppDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WhisperCppDemo.entitlements; sourceTree = "<group>"; };
34
  0AAC5DCD2953A05C003032C3 /* WhisperState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhisperState.swift; sourceTree = "<group>"; };
35
  0AAC5DD02953A394003032C3 /* LibWhisper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibWhisper.swift; sourceTree = "<group>"; };
 
 
36
  E3F92DC22AFA8DD800A6A9D4 /* whisper.cpp */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = whisper.cpp; path = ../..; sourceTree = "<group>"; };
37
  /* End PBXFileReference section */
38
 
@@ -52,6 +56,7 @@
52
  isa = PBXGroup;
53
  children = (
54
  0AAC5DCD2953A05C003032C3 /* WhisperState.swift */,
 
55
  );
56
  path = Models;
57
  sourceTree = "<group>";
@@ -119,6 +124,7 @@
119
  isa = PBXGroup;
120
  children = (
121
  0AAC5D9C29539CCF003032C3 /* ContentView.swift */,
 
122
  );
123
  path = UI;
124
  sourceTree = "<group>";
@@ -220,7 +226,9 @@
220
  0AAC5DCE2953A05C003032C3 /* WhisperState.swift in Sources */,
221
  0AAC5DD12953A394003032C3 /* LibWhisper.swift in Sources */,
222
  0AA7514C2953B569001EE061 /* RiffWaveUtils.swift in Sources */,
 
223
  0AA7514E2953D958001EE061 /* Recorder.swift in Sources */,
 
224
  );
225
  runOnlyForDeploymentPostprocessing = 0;
226
  };
 
17
  0AAC5D9F29539CD0003032C3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0AAC5D9E29539CD0003032C3 /* Assets.xcassets */; };
18
  0AAC5DCE2953A05C003032C3 /* WhisperState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AAC5DCD2953A05C003032C3 /* WhisperState.swift */; };
19
  0AAC5DD12953A394003032C3 /* LibWhisper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AAC5DD02953A394003032C3 /* LibWhisper.swift */; };
20
+ 7F79E0EE2CE0A78000ACD7BF /* DownloadButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F79E0ED2CE0A78000ACD7BF /* DownloadButton.swift */; };
21
+ 7F79E0F02CE0C6F700ACD7BF /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F79E0EF2CE0C6F700ACD7BF /* Model.swift */; };
22
  E3F92DC52AFA8E3800A6A9D4 /* whisper in Frameworks */ = {isa = PBXBuildFile; productRef = E3F92DC42AFA8E3800A6A9D4 /* whisper */; };
23
  /* End PBXBuildFile section */
24
 
 
35
  0AAC5DA029539CD0003032C3 /* WhisperCppDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WhisperCppDemo.entitlements; sourceTree = "<group>"; };
36
  0AAC5DCD2953A05C003032C3 /* WhisperState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhisperState.swift; sourceTree = "<group>"; };
37
  0AAC5DD02953A394003032C3 /* LibWhisper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LibWhisper.swift; sourceTree = "<group>"; };
38
+ 7F79E0ED2CE0A78000ACD7BF /* DownloadButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadButton.swift; sourceTree = "<group>"; };
39
+ 7F79E0EF2CE0C6F700ACD7BF /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
40
  E3F92DC22AFA8DD800A6A9D4 /* whisper.cpp */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = whisper.cpp; path = ../..; sourceTree = "<group>"; };
41
  /* End PBXFileReference section */
42
 
 
56
  isa = PBXGroup;
57
  children = (
58
  0AAC5DCD2953A05C003032C3 /* WhisperState.swift */,
59
+ 7F79E0EF2CE0C6F700ACD7BF /* Model.swift */,
60
  );
61
  path = Models;
62
  sourceTree = "<group>";
 
124
  isa = PBXGroup;
125
  children = (
126
  0AAC5D9C29539CCF003032C3 /* ContentView.swift */,
127
+ 7F79E0ED2CE0A78000ACD7BF /* DownloadButton.swift */,
128
  );
129
  path = UI;
130
  sourceTree = "<group>";
 
226
  0AAC5DCE2953A05C003032C3 /* WhisperState.swift in Sources */,
227
  0AAC5DD12953A394003032C3 /* LibWhisper.swift in Sources */,
228
  0AA7514C2953B569001EE061 /* RiffWaveUtils.swift in Sources */,
229
+ 7F79E0EE2CE0A78000ACD7BF /* DownloadButton.swift in Sources */,
230
  0AA7514E2953D958001EE061 /* Recorder.swift in Sources */,
231
+ 7F79E0F02CE0C6F700ACD7BF /* Model.swift in Sources */,
232
  );
233
  runOnlyForDeploymentPostprocessing = 0;
234
  };
include/whisper.h CHANGED
@@ -423,6 +423,14 @@ extern "C" {
423
  WHISPER_API whisper_token whisper_token_transcribe(struct whisper_context * ctx);
424
 
425
  // Performance information from the default state.
 
 
 
 
 
 
 
 
426
  WHISPER_API void whisper_print_timings(struct whisper_context * ctx);
427
  WHISPER_API void whisper_reset_timings(struct whisper_context * ctx);
428
 
 
423
  WHISPER_API whisper_token whisper_token_transcribe(struct whisper_context * ctx);
424
 
425
  // Performance information from the default state.
426
+ struct whisper_timings {
427
+ float sample_ms;
428
+ float encode_ms;
429
+ float decode_ms;
430
+ float batchd_ms;
431
+ float prompt_ms;
432
+ };
433
+ WHISPER_API struct whisper_timings * whisper_get_timings(struct whisper_context * ctx);
434
  WHISPER_API void whisper_print_timings(struct whisper_context * ctx);
435
  WHISPER_API void whisper_reset_timings(struct whisper_context * ctx);
436
 
src/whisper.cpp CHANGED
@@ -4186,6 +4186,19 @@ whisper_token whisper_token_transcribe(struct whisper_context * ctx) {
4186
  return ctx->vocab.token_transcribe;
4187
  }
4188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4189
  void whisper_print_timings(struct whisper_context * ctx) {
4190
  const int64_t t_end_us = ggml_time_us();
4191
 
 
4186
  return ctx->vocab.token_transcribe;
4187
  }
4188
 
4189
+ struct whisper_timings * whisper_get_timings(struct whisper_context * ctx) {
4190
+ if (ctx->state == nullptr) {
4191
+ return nullptr;
4192
+ }
4193
+ whisper_timings * timings = new whisper_timings;
4194
+ timings->sample_ms = 1e-3f * ctx->state->t_sample_us / std::max(1, ctx->state->n_sample);
4195
+ timings->encode_ms = 1e-3f * ctx->state->t_encode_us / std::max(1, ctx->state->n_encode);
4196
+ timings->decode_ms = 1e-3f * ctx->state->t_decode_us / std::max(1, ctx->state->n_decode);
4197
+ timings->batchd_ms = 1e-3f * ctx->state->t_batchd_us / std::max(1, ctx->state->n_batchd);
4198
+ timings->prompt_ms = 1e-3f * ctx->state->t_prompt_us / std::max(1, ctx->state->n_prompt);
4199
+ return timings;
4200
+ }
4201
+
4202
  void whisper_print_timings(struct whisper_context * ctx) {
4203
  const int64_t t_end_us = ggml_time_us();
4204