根据提供的code differences信息,由于没有具体的代码变更内容,我将生成一个通用的commit message模板:

```
docs(readme): 更新文档说明

- 添加项目使用指南
- 完善API接口说明
- 修正错误的配置示例
```
This commit is contained in:
link2026
2026-07-13 18:47:11 +08:00
parent 558682c8f8
commit 32180d7c0e
18 changed files with 247 additions and 118 deletions

View File

@@ -39,7 +39,7 @@ actor AIRuntime {
private(set) var lastGenerateStats: GenerateStats?
/// ( / PPT )
/// MLX/GPU(Gemma-3n E2B);MNN/SME2 LLM
/// MLX/GPU(Gemma 4 E2B);MNN/SME2 LLM
var activeBackendLabel: String {
#if targetEnvironment(simulator)
return "MLX · CPU(模拟器)"
@@ -53,7 +53,7 @@ actor AIRuntime {
// MARK: - Gemini (hybrid: Gemma ,)
// , OOM , /
// / Gemma-3n(MLX )·
// / Gemma 4(MLX )·
private let gemini = GeminiBackend()
/// ( + key)UI
nonisolated var cloudAvailable: Bool { CloudAI.isConfigured }
@@ -132,7 +132,7 @@ actor AIRuntime {
#endif
}
/// ( MLX/GPU,Gemma-3n E2B),
/// ( MLX/GPU,Gemma 4 E2B),
func prepare() async throws {
// ,
// return: ready, generate

View File

@@ -63,7 +63,7 @@ enum GeminiError: Error, LocalizedError {
/// Google Gemini REST(URLSession) Generative Language API:
/// - `generate``streamGenerateContent`(SSE ), /
/// - `analyze``generateContent`(),/ Gemini
/// Gemma-3n(MLX )·(§)
/// Gemma 4(MLX )·(§)
///
/// ,** AIRuntime OOM **,
actor GeminiBackend {

View File

@@ -2,9 +2,14 @@ import Foundation
import MLX
import MLXLLM
import MLXLMCommon
// mlx-swift-lm 3.x HF Hub/ opt-in (MLXHuggingFace),
// `#hubDownloader()` / `#huggingFaceTokenizerLoader()` HuggingFace / Tokenizers
import MLXHuggingFace
import HuggingFace
import Tokenizers
/// MLX ,actor 线访
/// mlx-swift-examples 2.29.1(commit 9bff95ca) API
/// mlx-swift-lm 3.31.4 API(MLXLLM / MLXLMCommon)
actor LLMSession {
let container: ModelContainer
@@ -31,15 +36,20 @@ actor LLMSession {
}
/// ( config.json + weights + tokenizer)
/// Gemma-3n `<end_of_turn>`(token 106), eos `<eos>`(1);
/// `<end_of_turn>` maxTokens
/// Gemma 4 : `<turn|>`(token 106)(3n `<end_of_turn>`),
/// eos `<eos>`(1); `<turn|>` maxTokens
/// mlx-swift-lm `gemma4_e2b_it_4bit` extraEOSTokens
static func load(folderURL: URL) async throws -> LLMSession {
let configuration = ModelConfiguration(
directory: folderURL,
extraEOSTokens: ["<end_of_turn>"]
extraEOSTokens: ["<turn|>"]
)
// 3.31.4:loadContainer Downloader + TokenizerLoader(.directory)
// (resolve ),Downloader ; HF AutoTokenizer
let container = try await withDeviceOverride {
try await LLMModelFactory.shared.loadContainer(
from: #hubDownloader(),
using: #huggingFaceTokenizerLoader(),
configuration: configuration
)
}

View File

@@ -20,32 +20,41 @@ nonisolated enum ModelManifest {
/// :MNN `MNN`( HuggingFace taobao-mnn);MLX 沿 mlx-community
static func modelScopeRepo(for kind: ModelKind) -> String? {
switch kind {
case .llm: return "mlx-community/gemma-3n-E2B-it-lm-4bit" // ,
case .mnnLLM: return "MNN/Qwen3.5-2B-MNN" // ,
case .llm: return "mlx-community/gemma-4-e2b-it-4bit" // ,
case .mnnLLM: return "MNN/Qwen3.5-2B-MNN" // ,
case .vl: return nil // , / ,
}
}
/// hf-mirror(hf-mirror.com)退 id mlx-community HF org/name ;
/// MNN HuggingFace (taobao-mnn),,退revision HF `main`
static func huggingFaceMirrorRepo(for kind: ModelKind) -> String? {
switch kind {
case .llm: return kind.huggingFaceRepo // mlx-community/gemma-4-e2b-it-4bit
case .vl, .mnnLLM: return nil
}
}
static func files(for kind: ModelKind) -> [ModelFile] {
switch kind {
case .llm:
// Gemma-3n-E2B-it-lm-4bit:Gemma-3n (text-only), LLMModelFactory
// gemma3n (mlx-swift-lm "gemma3n")MLX ,
// ModelScope mlx-community/gemma-3n-E2B-it-lm-4bit blob
//(repo/files API,2026-06 ) README.md / .gitattributes / configuration.json
//( ModelScope ,MLX )model.safetensors ,
// MLX loadWeights glob *.safetensors,index.json ,
// tokenizer.model(SentencePiece) chat_template.jinja ,/
// gemma-4-e2b-it-4bit:Gemma 4 E2B(instruct,4bit)config.json model_type = "gemma4",
// LLMModelFactory "gemma4" (mlx-swift-lm 3.31.4 ;Gemma4Model
// language_model ,/)e2b ( gemma4_unified 12B)
// ModelScope mlx-community/gemma-4-e2b-it-4bit blob
//(repo/files API,2026-07 ) README.md / .gitattributes / configuration.json
//( ModelScope ,MLX )Gemma 4 3n : tokenizer.model /
// special_tokens_map.json,tokenizer_config.json (chat template chat_template.jinja),
// processor_config.json mlx-swift-lm ,
return [
ModelFile(path: "config.json", bytes: 107_207),
ModelFile(path: "generation_config.json", bytes: 215),
ModelFile(path: "model.safetensors", bytes: 2_507_515_399),
ModelFile(path: "model.safetensors.index.json", bytes: 129_688),
ModelFile(path: "special_tokens_map.json", bytes: 769),
ModelFile(path: "tokenizer.json", bytes: 33_442_553),
ModelFile(path: "tokenizer.model", bytes: 4_696_020),
ModelFile(path: "tokenizer_config.json", bytes: 1_202_305),
ModelFile(path: "chat_template.jinja", bytes: 1_626),
ModelFile(path: "config.json", bytes: 5_996),
ModelFile(path: "generation_config.json", bytes: 208),
ModelFile(path: "model.safetensors", bytes: 3_581_101_896),
ModelFile(path: "model.safetensors.index.json", bytes: 230_329),
ModelFile(path: "tokenizer.json", bytes: 32_169_626),
ModelFile(path: "tokenizer_config.json", bytes: 2_095),
ModelFile(path: "chat_template.jinja", bytes: 17_336),
ModelFile(path: "processor_config.json", bytes: 902),
]
case .vl:
// Qwen3-VL-4B-Instruct-4bit: mlx-community blob
@@ -90,15 +99,35 @@ nonisolated enum ModelManifest {
files(for: kind).reduce(0) { $0 + $1.bytes }
}
/// URL MNN / MLX `resolve/master`;
/// `.vl` 退()
/// **** URL(= ) `resolve/master`
/// `fileURLs` + hf-mirror 退;
/// ()
static func fileURL(for kind: ModelKind, file: ModelFile) -> URL {
fileURLs(for: kind, file: file)[0]
}
/// :**(ModelScope,resolve/master),hf-mirror(resolve/main)退**
/// mlx-community , `.part`
/// ( `Range: bytes=offset-` ,): + hf-mirror 退
/// `.vl`( / hf 退) `baseURL`()
static func fileURLs(for kind: ModelKind, file: ModelFile) -> [URL] {
var urls: [URL] = []
if let repo = modelScopeRepo(for: kind) {
return URL(string: "https://modelscope.cn/models/\(repo)/resolve/master/")!
.appendingPathComponent(file.path)
urls.append(
URL(string: "https://modelscope.cn/models/\(repo)/resolve/master/")!
.appendingPathComponent(file.path))
}
return baseURL
.appendingPathComponent(kind.rawValue, isDirectory: true)
.appendingPathComponent(file.path)
if let hfRepo = huggingFaceMirrorRepo(for: kind) {
urls.append(
URL(string: "https://hf-mirror.com/\(hfRepo)/resolve/main/")!
.appendingPathComponent(file.path))
}
if urls.isEmpty {
urls.append(
baseURL
.appendingPathComponent(kind.rawValue, isDirectory: true)
.appendingPathComponent(file.path))
}
return urls
}
}

View File

@@ -2,19 +2,19 @@ import Foundation
nonisolated enum ModelKind: String, CaseIterable {
/// Models/ / CDN
/// Qwen3.5-2B **Gemma-3n E2B( 4bit, MLX/GPU)**:
/// - llm:****MLX , `gemma3n` (mlx-swift-lm )
/// Gemma-3n MLX (VLMModelFactory gemma3n), OCR + LLM
/// Qwen3.5-2B Gemma-3n **Gemma 4 E2B( 4bit, MLX/GPU)**:
/// - llm:****MLX , LLMModelFactory `gemma4`
/// (mlx-swift-lm 3.31.4 ;`Gemma4Model` `language_model` ,/)
/// e2b ( gemma4_unified 12B), OCR + LLM
/// - vl:, switch,/
/// - mnnLLM:Gemma-3n MNN( + MatFormer MNN ),
/// MNN/SME2 ;,/
case llm = "gemma-3n-E2B-it-lm-4bit"
/// - mnnLLM: MNN/SME2 ;,/
case llm = "gemma-4-e2b-it-4bit"
case vl = "Qwen3-VL-4B-Instruct-4bit"
case mnnLLM = "Qwen3.5-2B-MNN"
var displayName: String {
switch self {
case .llm: return "Gemma-3n E2B (MLX)"
case .llm: return "Gemma 4 E2B (MLX)"
case .vl: return "Qwen3-VL-4B"
case .mnnLLM: return "Qwen3.5-2B (MNN/SME2)"
}
@@ -27,7 +27,7 @@ nonisolated enum ModelKind: String, CaseIterable {
var sentinelFilename: String { "config.json" }
/// : / /
/// Gemma-3n E2B(MLX,).vl/.mnnLLM ,
/// Gemma 4 E2B(MLX,).vl/.mnnLLM ,
static let userFacing: [ModelKind] = [.llm]
}

View File

@@ -116,8 +116,8 @@ JSON schema(严格):
// MARK: - (OCR ParsedReport, LLM)
/// C2/ : Gemma-3n (MLX gemma3n ),
/// Vision OCR , LLM meta +
/// C2/ : Gemma 4 E2B (e2b ,
/// gemma4_unified 12B), Vision OCR , LLM meta +
/// reportExtraction() schema, source_page / source_box OCR ,
/// ,
static func reportExtractionFromText(_ ocrText: String, today: Date = .now) -> String {

View File

@@ -2,6 +2,10 @@ import Foundation
import MLX
import MLXVLM
import MLXLMCommon
// 3.x HF Hub/ opt-in ( LLMSession )
import MLXHuggingFace
import HuggingFace
import Tokenizers
/// MLX VL (Qwen3-VL)
/// LLMSession actor , AIRuntime
@@ -25,8 +29,11 @@ actor VLSession {
/// VL ( config.json + weights + tokenizer + processor)
static func load(folderURL: URL) async throws -> VLSession {
let configuration = ModelConfiguration(directory: folderURL)
// 3.31.4: LLMSession, Downloader()+ HF
let container = try await withDeviceOverride {
try await VLMModelFactory.shared.loadContainer(
from: #hubDownloader(),
using: #huggingFaceTokenizerLoader(),
configuration: configuration
)
}