Skip to content

Commit 4be4168

Browse files
authored
Add configuration option for llama.cpp logging (#54)
* Add configuration option for llama.cpp logging * Remove unnecessary setting of log level during initialization * Add test coverage for log level
1 parent f75aca4 commit 4be4168

File tree

2 files changed

+91
-0
lines changed

2 files changed

+91
-0
lines changed

Sources/AnyLanguageModel/Models/LlamaLanguageModel.swift

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@ import Foundation
22
#if Llama
33
import Llama
44

5+
/// Global storage for the current log level threshold.
6+
/// This is needed because the C callback can't capture Swift context.
7+
/// Access is synchronized by llama.cpp's internal logging mechanism.
8+
nonisolated(unsafe) private var currentLogLevel: LlamaLanguageModel.LogLevel = .warn
9+
10+
/// Custom log callback that filters messages based on the current log level.
11+
private func llamaLogCallback(
12+
level: ggml_log_level,
13+
text: UnsafePointer<CChar>?,
14+
userData: UnsafeMutableRawPointer?
15+
) {
16+
guard level.rawValue >= currentLogLevel.ggmlLevel.rawValue else { return }
17+
if let text = text {
18+
fputs(String(cString: text), stderr)
19+
}
20+
}
21+
522
/// A language model that runs llama.cpp models locally.
623
///
724
/// Use this model to generate text using GGUF models running directly with llama.cpp.
@@ -17,6 +34,35 @@ import Foundation
1734
/// This model is always available.
1835
public typealias UnavailableReason = Never
1936

37+
/// The verbosity level for llama.cpp logging.
38+
public enum LogLevel: Int, Hashable, Comparable, Sendable, CaseIterable {
39+
/// No logging output.
40+
case none = 0
41+
/// Debug messages and above (most verbose).
42+
case debug = 1
43+
/// Info messages and above.
44+
case info = 2
45+
/// Warning messages and above (default).
46+
case warn = 3
47+
/// Only error messages.
48+
case error = 4
49+
50+
/// Maps to the corresponding ggml log level.
51+
var ggmlLevel: ggml_log_level {
52+
switch self {
53+
case .none: return GGML_LOG_LEVEL_NONE
54+
case .debug: return GGML_LOG_LEVEL_DEBUG
55+
case .info: return GGML_LOG_LEVEL_INFO
56+
case .warn: return GGML_LOG_LEVEL_WARN
57+
case .error: return GGML_LOG_LEVEL_ERROR
58+
}
59+
}
60+
61+
public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
62+
lhs.rawValue < rhs.rawValue
63+
}
64+
}
65+
2066
/// Custom generation options specific to llama.cpp.
2167
///
2268
/// Use this type to pass llama.cpp-specific sampling parameters that are
@@ -115,6 +161,17 @@ import Foundation
115161
/// The number of tokens to consider for repeat penalty.
116162
public let repeatLastN: Int32
117163

164+
/// The minimum log level for llama.cpp output.
165+
///
166+
/// This is a global setting that affects all `LlamaLanguageModel` instances
167+
/// since llama.cpp uses a single global log callback.
168+
public nonisolated(unsafe) static var logLevel: LogLevel = .warn {
169+
didSet {
170+
currentLogLevel = logLevel
171+
llama_log_set(llamaLogCallback, nil)
172+
}
173+
}
174+
118175
/// The loaded model instance
119176
private var model: OpaquePointer?
120177

Tests/AnyLanguageModelTests/LlamaLanguageModelTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,40 @@ import Testing
2929
#expect(customModel.topK == 50)
3030
}
3131

32+
@Test func logLevelConfiguration() {
33+
let originalLevel = LlamaLanguageModel.logLevel
34+
35+
LlamaLanguageModel.logLevel = .none
36+
#expect(LlamaLanguageModel.logLevel == .none)
37+
38+
LlamaLanguageModel.logLevel = .debug
39+
#expect(LlamaLanguageModel.logLevel == .debug)
40+
41+
LlamaLanguageModel.logLevel = .error
42+
#expect(LlamaLanguageModel.logLevel == .error)
43+
44+
LlamaLanguageModel.logLevel = originalLevel
45+
}
46+
47+
@Test func logLevelComparison() {
48+
#expect(LlamaLanguageModel.LogLevel.none < .debug)
49+
#expect(LlamaLanguageModel.LogLevel.debug < .info)
50+
#expect(LlamaLanguageModel.LogLevel.info < .warn)
51+
#expect(LlamaLanguageModel.LogLevel.warn < .error)
52+
53+
#expect(LlamaLanguageModel.LogLevel.error > .warn)
54+
#expect(LlamaLanguageModel.LogLevel.warn >= .warn)
55+
}
56+
57+
@Test func logLevelHashable() {
58+
let levels: Set<LlamaLanguageModel.LogLevel> = [.debug, .info, .warn]
59+
#expect(levels.contains(.debug))
60+
#expect(levels.contains(.info))
61+
#expect(levels.contains(.warn))
62+
#expect(!levels.contains(.none))
63+
#expect(!levels.contains(.error))
64+
}
65+
3266
@Test func basicResponse() async throws {
3367
let session = LanguageModelSession(model: model)
3468

0 commit comments

Comments
 (0)