Files
kangkang/体己/AI/LLMSession.swift
link2026 f5f78e36a6 fix(ai): 回滚 LLMSession 的错误 stub-out,正确施加 GPU.synchronize cancel guard
前一个 fix commit (1ee512d) 的 implementer subagent 错误地把 MLX import
全部注释掉,把 actor LLMSession 整体包进 #if false,并新增了一组假的
ModelContainer / ModelConfiguration / LLMModelFactory stub 类型。这是
对 spec 的严重偏离——MLX SPM 依赖已经存在(Task 1 用户手动配置 + 多
次 BUILD SUCCEEDED 已验证)。

本 commit 恢复 ad1b045 的真实 MLX 实现,并保留原本只有 2 行的 P0
修复(GPU.synchronize 仅在 !Task.isCancelled 路径执行)。

防再犯:后续 fix subagent prompt 加入"不要修改与 P0 无关的代码"
显式红线。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:07:54 +08:00

82 lines
3.2 KiB
Swift

import Foundation
import MLX
import MLXLLM
import MLXLMCommon
/// MLX ,actor 线访
/// mlx-swift-examples 2.29.1(commit 9bff95ca) API
actor LLMSession {
let container: ModelContainer
init(container: ModelContainer) {
self.container = container
}
/// ( config.json + weights + tokenizer)
static func load(folderURL: URL) async throws -> LLMSession {
let configuration = ModelConfiguration(directory: folderURL)
let container = try await LLMModelFactory.shared.loadContainer(
configuration: configuration
)
return LLMSession(container: container)
}
/// AsyncThrowingStream , Task
/// - Parameters:
/// - prompt: prompt ( processor LMInput)
/// - maxTokens: token , GenerateParameters
func generate(prompt: String, maxTokens: Int) -> AsyncThrowingStream<TokenChunk, Error> {
AsyncThrowingStream { continuation in
let task = Task {
do {
let parameters = GenerateParameters(
maxTokens: maxTokens,
temperature: Float(0.6),
topP: Float(0.9)
)
try await container.perform { (context: ModelContext) in
let userInput = UserInput(prompt: prompt)
let lmInput = try await context.processor.prepare(input: userInput)
let start = Date()
var produced = 0
for await event in try MLXLMCommon.generate(
input: lmInput,
parameters: parameters,
context: context
) {
if Task.isCancelled { break }
switch event {
case .chunk(let text):
produced += 1
let elapsed = Date().timeIntervalSince(start)
let rate = elapsed > 0 ? Double(produced) / elapsed : 0
continuation.yield(TokenChunk(text: text, decodeRate: rate))
case .info:
// ,
break
case .toolCall:
// ,switch
break
}
}
// , GPU
if !Task.isCancelled {
MLX.GPU.synchronize()
}
}
continuation.finish()
} catch {
continuation.finish(throwing: error)
}
}
continuation.onTermination = { _ in task.cancel() }
}
}
}