telos-ml 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- telos/__init__.py +74 -0
- telos/__main__.py +9 -0
- telos/afm/__init__.py +67 -0
- telos/afm/_swift/afm_bridge.swift +136 -0
- telos/afm/availability.py +195 -0
- telos/afm/bridge.py +246 -0
- telos/afm/cli.py +96 -0
- telos/assets/__init__.py +1 -0
- telos/assets/tokenizer_0.json +20145 -0
- telos/bench/__init__.py +15 -0
- telos/bench/__main__.py +4 -0
- telos/bench/runner.py +81 -0
- telos/cli.py +73 -0
- telos/configs/__init__.py +9 -0
- telos/configs/builder.py +460 -0
- telos/configs/solver.py +142 -0
- telos/data/__init__.py +21 -0
- telos/data/tokenizer.py +91 -0
- telos/dataprep/__init__.py +16 -0
- telos/dataprep/__main__.py +4 -0
- telos/dataprep/prepare.py +348 -0
- telos/diffusion/__init__.py +90 -0
- telos/diffusion/ar.py +88 -0
- telos/diffusion/corosred.py +628 -0
- telos/diffusion/corosred_unified.py +363 -0
- telos/diffusion/mdlm.py +164 -0
- telos/diffusion/refiner.py +148 -0
- telos/diffusion/region.py +236 -0
- telos/diffusion/sampler.py +237 -0
- telos/diffusion/undlm.py +119 -0
- telos/eval/__init__.py +69 -0
- telos/eval/__main__.py +4 -0
- telos/eval/adapters/__init__.py +20 -0
- telos/eval/adapters/base.py +78 -0
- telos/eval/adapters/gemini_api.py +129 -0
- telos/eval/adapters/huggingface.py +99 -0
- telos/eval/adapters/mlx_lm.py +51 -0
- telos/eval/adapters/openai_api.py +179 -0
- telos/eval/adapters/registry.py +132 -0
- telos/eval/adapters/swift_afm.py +84 -0
- telos/eval/adapters/telos_native.py +82 -0
- telos/eval/anticheat.py +217 -0
- telos/eval/contamination.py +129 -0
- telos/eval/cyber_eval.py +120 -0
- telos/eval/executor.py +220 -0
- telos/eval/linguistic/__init__.py +19 -0
- telos/eval/linguistic/evaluator.py +442 -0
- telos/eval/linguistic/probes_en.py +1162 -0
- telos/eval/metrics.py +208 -0
- telos/eval/polyglot_eval.py +207 -0
- telos/eval/probes.py +160 -0
- telos/eval/reasoning_eval.py +167 -0
- telos/eval/runner.py +1562 -0
- telos/eval/stats.py +46 -0
- telos/eval/syntax.py +114 -0
- telos/eval/tooluse/__init__.py +21 -0
- telos/eval/tooluse/evaluator.py +326 -0
- telos/eval/tooluse/suite.py +341 -0
- telos/models/__init__.py +25 -0
- telos/models/components.py +143 -0
- telos/models/config.py +26 -0
- telos/models/mlx_components.py +106 -0
- telos/models/mlx_transformer.py +95 -0
- telos/models/param_counter.py +65 -0
- telos/models/transformer.py +107 -0
- telos/models/upscale.py +45 -0
- telos/testing/__init__.py +11 -0
- telos/testing/suite.py +329 -0
- telos/train/__init__.py +15 -0
- telos/train/__main__.py +4 -0
- telos/train/cli.py +514 -0
- telos/training/__init__.py +63 -0
- telos/training/core.py +233 -0
- telos/training/dataloader.py +68 -0
- telos/training/hardware.py +102 -0
- telos/training/lr_schedule.py +46 -0
- telos/training/monitor.py +188 -0
- telos/training/schedule.py +296 -0
- telos/training/trainer_mlx.py +547 -0
- telos/training/trainer_pytorch.py +1046 -0
- telos/training/xla_utils.py +180 -0
- telos_ml-1.0.0.dist-info/METADATA +234 -0
- telos_ml-1.0.0.dist-info/RECORD +86 -0
- telos_ml-1.0.0.dist-info/WHEEL +4 -0
- telos_ml-1.0.0.dist-info/entry_points.txt +2 -0
- telos_ml-1.0.0.dist-info/licenses/LICENSE +190 -0
telos/__init__.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
télos (τέλος): Discrete Diffusion & Autoregressive Language Modeling Framework.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib.metadata import version as _pkg_version, PackageNotFoundError
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
__version__ = _pkg_version("telos-ml")
|
|
9
|
+
except PackageNotFoundError:
|
|
10
|
+
try:
|
|
11
|
+
__version__ = _pkg_version("telos")
|
|
12
|
+
except PackageNotFoundError:
|
|
13
|
+
__version__ = "1.0.0"
|
|
14
|
+
|
|
15
|
+
from . import dataprep
|
|
16
|
+
from . import train
|
|
17
|
+
from . import eval
|
|
18
|
+
from . import bench
|
|
19
|
+
from .training.hardware import HardwareProfile
|
|
20
|
+
|
|
21
|
+
from .models.config import TelosConfig
|
|
22
|
+
|
|
23
|
+
# Primary callable aliases
|
|
24
|
+
prepare_dataset = dataprep.prepare_dataset
|
|
25
|
+
run_train = train.train
|
|
26
|
+
evaluate = eval.evaluate
|
|
27
|
+
benchmark = bench.benchmark
|
|
28
|
+
|
|
29
|
+
_TORCH_IMPORTS = {
|
|
30
|
+
"TelosTransformer": ".models.transformer",
|
|
31
|
+
"UnifiedPyTorchTrainer": ".training.trainer_pytorch",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def __getattr__(name: str):
|
|
35
|
+
if name == "afm":
|
|
36
|
+
import importlib
|
|
37
|
+
return importlib.import_module(".afm", __package__)
|
|
38
|
+
if name == "MLXTelosTransformer":
|
|
39
|
+
from .models import MLXTelosTransformer
|
|
40
|
+
return MLXTelosTransformer
|
|
41
|
+
if name == "UnifiedMLXTrainer":
|
|
42
|
+
from .training import UnifiedMLXTrainer
|
|
43
|
+
return UnifiedMLXTrainer
|
|
44
|
+
if name in _TORCH_IMPORTS:
|
|
45
|
+
try:
|
|
46
|
+
import importlib
|
|
47
|
+
mod = importlib.import_module(_TORCH_IMPORTS[name], __package__)
|
|
48
|
+
return getattr(mod, name)
|
|
49
|
+
except ImportError as err:
|
|
50
|
+
raise ImportError(
|
|
51
|
+
f"{name} requires 'torch', which is not available in this environment. "
|
|
52
|
+
"Install it via `pip install torch`."
|
|
53
|
+
) from err
|
|
54
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"__version__",
|
|
58
|
+
"dataprep",
|
|
59
|
+
"train",
|
|
60
|
+
"run_train",
|
|
61
|
+
"eval",
|
|
62
|
+
"bench",
|
|
63
|
+
"prepare_dataset",
|
|
64
|
+
"evaluate",
|
|
65
|
+
"benchmark",
|
|
66
|
+
"afm",
|
|
67
|
+
"TelosTransformer",
|
|
68
|
+
"TelosConfig",
|
|
69
|
+
"MLXTelosTransformer",
|
|
70
|
+
"UnifiedMLXTrainer",
|
|
71
|
+
"UnifiedPyTorchTrainer",
|
|
72
|
+
"HardwareProfile",
|
|
73
|
+
]
|
|
74
|
+
|
telos/__main__.py
ADDED
telos/afm/__init__.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
telos.afm
|
|
3
|
+
Python access to Apple's on-device Foundation Models.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
>>> import telos.afm
|
|
7
|
+
>>> telos.afm.probe().describe()
|
|
8
|
+
>>> telos.afm.generate("Write a haiku about gradients.")
|
|
9
|
+
|
|
10
|
+
Requires macOS 26+, an Apple Intelligence-eligible Apple Silicon Mac with it
|
|
11
|
+
enabled, and the Xcode Command Line Tools. The Swift bridge is built on first
|
|
12
|
+
use into ~/.cache/telos (override with TELOS_CACHE_DIR).
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Optional, Sequence
|
|
18
|
+
|
|
19
|
+
from .availability import (
|
|
20
|
+
AFMAvailability,
|
|
21
|
+
AFMUnavailableError,
|
|
22
|
+
FALLBACK_CHIP_FAMILY,
|
|
23
|
+
MIN_MACOS_MAJOR,
|
|
24
|
+
MODEL_CORE,
|
|
25
|
+
MODEL_CORE_ADVANCED,
|
|
26
|
+
probe,
|
|
27
|
+
require,
|
|
28
|
+
)
|
|
29
|
+
from .bridge import AFMBridge, cache_root, ensure_bridge_binary
|
|
30
|
+
|
|
31
|
+
__all__ = [
|
|
32
|
+
"AFMAvailability",
|
|
33
|
+
"AFMBridge",
|
|
34
|
+
"AFMUnavailableError",
|
|
35
|
+
"FALLBACK_CHIP_FAMILY",
|
|
36
|
+
"MIN_MACOS_MAJOR",
|
|
37
|
+
"MODEL_CORE",
|
|
38
|
+
"MODEL_CORE_ADVANCED",
|
|
39
|
+
"cache_root",
|
|
40
|
+
"ensure_bridge_binary",
|
|
41
|
+
"generate",
|
|
42
|
+
"probe",
|
|
43
|
+
"require",
|
|
44
|
+
]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def generate(
|
|
48
|
+
prompt: str,
|
|
49
|
+
*,
|
|
50
|
+
max_new_tokens: int = 256,
|
|
51
|
+
temperature: float = 0.0,
|
|
52
|
+
stop: Optional[Sequence[str]] = None,
|
|
53
|
+
instructions: Optional[str] = None,
|
|
54
|
+
) -> str:
|
|
55
|
+
"""Generates one completion on the on-device model.
|
|
56
|
+
|
|
57
|
+
Opens a bridge, generates, and closes it. Hold an AFMBridge open for
|
|
58
|
+
repeated calls so the process and model stay warm.
|
|
59
|
+
"""
|
|
60
|
+
with AFMBridge() as bridge:
|
|
61
|
+
return bridge.generate(
|
|
62
|
+
prompt,
|
|
63
|
+
max_new_tokens=max_new_tokens,
|
|
64
|
+
temperature=temperature,
|
|
65
|
+
stop=stop,
|
|
66
|
+
instructions=instructions,
|
|
67
|
+
)
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import FoundationModels
|
|
3
|
+
|
|
4
|
+
struct GenerationRequest: Codable {
|
|
5
|
+
let id: Int?
|
|
6
|
+
let prompt: String
|
|
7
|
+
let instructions: String?
|
|
8
|
+
let maxTokens: Int?
|
|
9
|
+
let temperature: Double?
|
|
10
|
+
let stopTokens: [String]?
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
struct GenerationResponse: Codable {
|
|
14
|
+
let id: Int?
|
|
15
|
+
let content: String
|
|
16
|
+
let error: String?
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// One-shot availability report, emitted for `afm_bridge --status`.
|
|
20
|
+
///
|
|
21
|
+
/// `FoundationModels` exposes no public selector for a specific on-device
|
|
22
|
+
/// model, only `SystemLanguageModel.default`. The `check` field therefore
|
|
23
|
+
/// reports *why* the default model is or is not usable, and the Python side
|
|
24
|
+
/// maps that onto the Core / Core Advanced preference.
|
|
25
|
+
struct StatusResponse: Codable {
|
|
26
|
+
let available: Bool
|
|
27
|
+
/// One of: "available", "device_not_eligible",
|
|
28
|
+
/// "apple_intelligence_not_enabled", "model_not_ready", "unknown".
|
|
29
|
+
let check: String
|
|
30
|
+
let detail: String?
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func describeAvailability(_ model: SystemLanguageModel) -> StatusResponse {
|
|
34
|
+
switch model.availability {
|
|
35
|
+
case .available:
|
|
36
|
+
return StatusResponse(available: true, check: "available", detail: nil)
|
|
37
|
+
case .unavailable(let reason):
|
|
38
|
+
switch reason {
|
|
39
|
+
case .deviceNotEligible:
|
|
40
|
+
return StatusResponse(
|
|
41
|
+
available: false,
|
|
42
|
+
check: "device_not_eligible",
|
|
43
|
+
detail: "This device is not eligible for Apple Intelligence."
|
|
44
|
+
)
|
|
45
|
+
case .appleIntelligenceNotEnabled:
|
|
46
|
+
return StatusResponse(
|
|
47
|
+
available: false,
|
|
48
|
+
check: "apple_intelligence_not_enabled",
|
|
49
|
+
detail: "Apple Intelligence is not enabled in System Settings."
|
|
50
|
+
)
|
|
51
|
+
case .modelNotReady:
|
|
52
|
+
return StatusResponse(
|
|
53
|
+
available: false,
|
|
54
|
+
check: "model_not_ready",
|
|
55
|
+
detail: "The on-device model is still downloading or preparing."
|
|
56
|
+
)
|
|
57
|
+
@unknown default:
|
|
58
|
+
return StatusResponse(available: false, check: "unknown", detail: "\(reason)")
|
|
59
|
+
}
|
|
60
|
+
@unknown default:
|
|
61
|
+
return StatusResponse(available: false, check: "unknown", detail: nil)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
func emitJSON<T: Encodable>(_ value: T) {
|
|
66
|
+
if let data = try? JSONEncoder().encode(value),
|
|
67
|
+
let str = String(data: data, encoding: .utf8) {
|
|
68
|
+
print(str)
|
|
69
|
+
fflush(stdout)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@main
|
|
74
|
+
struct AFMBridge {
|
|
75
|
+
static func main() async {
|
|
76
|
+
let model = SystemLanguageModel.default
|
|
77
|
+
|
|
78
|
+
// One-shot status probe: `afm_bridge --status`. Exits immediately so
|
|
79
|
+
// the Python side can report availability without opening a session.
|
|
80
|
+
if CommandLine.arguments.contains("--status") {
|
|
81
|
+
emitJSON(describeAvailability(model))
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
guard model.isAvailable else {
|
|
86
|
+
let resp = GenerationResponse(id: nil, content: "", error: "SystemLanguageModel is not available on this system.")
|
|
87
|
+
emitJSON(resp)
|
|
88
|
+
return
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let stdin = FileHandle.standardInput
|
|
92
|
+
while let lineData = try? stdin.readLine() {
|
|
93
|
+
guard !lineData.isEmpty else { continue }
|
|
94
|
+
guard let req = try? JSONDecoder().decode(GenerationRequest.self, from: lineData) else {
|
|
95
|
+
continue
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
let instructions = req.instructions ?? "You are an expert programming and reasoning assistant. Provide direct, concise, and accurate output without conversational filler."
|
|
99
|
+
let session = LanguageModelSession(model: model, instructions: instructions)
|
|
100
|
+
let options = GenerationOptions(
|
|
101
|
+
temperature: req.temperature ?? 0.0,
|
|
102
|
+
maximumResponseTokens: req.maxTokens ?? 256
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
do {
|
|
106
|
+
let response = try await session.respond(to: req.prompt, options: options)
|
|
107
|
+
var text = response.content
|
|
108
|
+
if let stopTokens = req.stopTokens {
|
|
109
|
+
for stop in stopTokens {
|
|
110
|
+
if let range = text.range(of: stop) {
|
|
111
|
+
text = String(text[..<range.lowerBound])
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
emitJSON(GenerationResponse(id: req.id, content: text, error: nil))
|
|
116
|
+
} catch {
|
|
117
|
+
emitJSON(GenerationResponse(id: req.id, content: "", error: "\(error)"))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
extension FileHandle {
|
|
124
|
+
func readLine() throws -> Data? {
|
|
125
|
+
var buffer = Data()
|
|
126
|
+
while true {
|
|
127
|
+
guard let byteData = try self.read(upToCount: 1), !byteData.isEmpty else {
|
|
128
|
+
return buffer.isEmpty ? nil : buffer
|
|
129
|
+
}
|
|
130
|
+
if byteData[0] == 10 { // newline \n
|
|
131
|
+
return buffer
|
|
132
|
+
}
|
|
133
|
+
buffer.append(byteData)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AFM Availability
|
|
3
|
+
Detects whether Apple's on-device Foundation Models are usable on this machine.
|
|
4
|
+
|
|
5
|
+
FoundationModels exposes only `SystemLanguageModel.default`, so this detects
|
|
6
|
+
eligibility and names what is called rather than switching models at runtime.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import platform
|
|
12
|
+
import re
|
|
13
|
+
import shutil
|
|
14
|
+
import subprocess
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
from .bridge import AFMUnavailableError, query_status
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"AFMAvailability",
|
|
22
|
+
"AFMUnavailableError",
|
|
23
|
+
"MIN_MACOS_MAJOR",
|
|
24
|
+
"FALLBACK_CHIP_FAMILY",
|
|
25
|
+
"probe",
|
|
26
|
+
"require",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
MIN_MACOS_MAJOR = 26
|
|
30
|
+
FALLBACK_CHIP_FAMILY = "M3"
|
|
31
|
+
|
|
32
|
+
MODEL_CORE_ADVANCED = "afm-3-core-advanced"
|
|
33
|
+
MODEL_CORE = "afm-3-core"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class AFMAvailability:
|
|
38
|
+
"""Outcome of an on-device Foundation Models probe."""
|
|
39
|
+
|
|
40
|
+
available: bool
|
|
41
|
+
#: Name of the model being called, or None when unavailable.
|
|
42
|
+
model: Optional[str]
|
|
43
|
+
#: Smaller model offered as fallback on M3+, or None on older silicon.
|
|
44
|
+
fallback_model: Optional[str]
|
|
45
|
+
#: "ok" when available, otherwise why not.
|
|
46
|
+
reason: str
|
|
47
|
+
macos_version: Optional[tuple[int, ...]]
|
|
48
|
+
chip_family: Optional[str]
|
|
49
|
+
framework_check: Optional[str]
|
|
50
|
+
xcode_clt: bool
|
|
51
|
+
swiftc: bool
|
|
52
|
+
|
|
53
|
+
def describe(self) -> str:
|
|
54
|
+
"""One-line human summary."""
|
|
55
|
+
if self.available:
|
|
56
|
+
line = f"{self.model} available (Apple on-device Foundation Models)."
|
|
57
|
+
if self.fallback_model:
|
|
58
|
+
line += f" Fallback on this chip: {self.fallback_model}."
|
|
59
|
+
return line
|
|
60
|
+
return f"Apple on-device Foundation Models unavailable: {self.reason}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _parse_macos_major(ver: str) -> Optional[tuple[int, ...]]:
|
|
64
|
+
parts = []
|
|
65
|
+
for chunk in ver.split("."):
|
|
66
|
+
if not chunk.isdigit():
|
|
67
|
+
break
|
|
68
|
+
parts.append(int(chunk))
|
|
69
|
+
return tuple(parts) if parts else None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _chip_family() -> Optional[str]:
|
|
73
|
+
# sysctl can be blocked by a sandbox; unknown is fine, runtime check decides.
|
|
74
|
+
try:
|
|
75
|
+
res = subprocess.run(
|
|
76
|
+
["sysctl", "-n", "machdep.cpu.brand_string"],
|
|
77
|
+
capture_output=True,
|
|
78
|
+
text=True,
|
|
79
|
+
timeout=5,
|
|
80
|
+
)
|
|
81
|
+
except (OSError, subprocess.SubprocessError):
|
|
82
|
+
return None
|
|
83
|
+
if res.returncode != 0:
|
|
84
|
+
return None
|
|
85
|
+
match = re.search(r"\bApple\s+(M\d+)", res.stdout)
|
|
86
|
+
return match.group(1) if match else None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _chip_at_least(family: Optional[str], minimum: str) -> bool:
|
|
90
|
+
if family is None:
|
|
91
|
+
return False
|
|
92
|
+
m = re.fullmatch(r"M(\d+)", family)
|
|
93
|
+
n = re.fullmatch(r"M(\d+)", minimum)
|
|
94
|
+
if not m or not n:
|
|
95
|
+
return False
|
|
96
|
+
return int(m.group(1)) >= int(n.group(1))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _has_xcode_clt() -> bool:
|
|
100
|
+
try:
|
|
101
|
+
res = subprocess.run(["xcode-select", "-p"], capture_output=True, text=True, timeout=5)
|
|
102
|
+
except (OSError, subprocess.SubprocessError):
|
|
103
|
+
return False
|
|
104
|
+
return res.returncode == 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def probe(*, runtime: bool = True) -> AFMAvailability:
|
|
108
|
+
"""Detects whether the on-device models are usable. Never raises for an
|
|
109
|
+
unsupported machine; inspect `available` and `reason` instead."""
|
|
110
|
+
if platform.system() != "Darwin":
|
|
111
|
+
return AFMAvailability(
|
|
112
|
+
available=False,
|
|
113
|
+
model=None,
|
|
114
|
+
fallback_model=None,
|
|
115
|
+
reason=f"Apple Foundation Models are macOS-only (this is {platform.system()}).",
|
|
116
|
+
macos_version=None,
|
|
117
|
+
chip_family=None,
|
|
118
|
+
framework_check=None,
|
|
119
|
+
xcode_clt=False,
|
|
120
|
+
swiftc=False,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
macos_ver = _parse_macos_major(platform.mac_ver()[0])
|
|
124
|
+
chip = _chip_family()
|
|
125
|
+
has_clt = _has_xcode_clt()
|
|
126
|
+
has_swiftc = shutil.which("swiftc") is not None
|
|
127
|
+
|
|
128
|
+
def unavailable(reason: str, framework_check: Optional[str] = None) -> AFMAvailability:
|
|
129
|
+
return AFMAvailability(
|
|
130
|
+
available=False,
|
|
131
|
+
model=None,
|
|
132
|
+
fallback_model=None,
|
|
133
|
+
reason=reason,
|
|
134
|
+
macos_version=macos_ver,
|
|
135
|
+
chip_family=chip,
|
|
136
|
+
framework_check=framework_check,
|
|
137
|
+
xcode_clt=has_clt,
|
|
138
|
+
swiftc=has_swiftc,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if platform.machine() != "arm64":
|
|
142
|
+
return unavailable(f"Apple Foundation Models require Apple Silicon (this is {platform.machine()}).")
|
|
143
|
+
if macos_ver is None:
|
|
144
|
+
return unavailable("Could not determine the macOS version.")
|
|
145
|
+
if macos_ver[0] < MIN_MACOS_MAJOR:
|
|
146
|
+
return unavailable(
|
|
147
|
+
f"Apple Foundation Models require macOS {MIN_MACOS_MAJOR} or later "
|
|
148
|
+
f"(this is {'.'.join(map(str, macos_ver))})."
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
fallback = MODEL_CORE if _chip_at_least(chip, FALLBACK_CHIP_FAMILY) else None
|
|
152
|
+
|
|
153
|
+
if not runtime:
|
|
154
|
+
# Platform can host the framework; Apple Intelligence state is runtime-only.
|
|
155
|
+
return AFMAvailability(
|
|
156
|
+
available=True,
|
|
157
|
+
model=MODEL_CORE_ADVANCED,
|
|
158
|
+
fallback_model=fallback,
|
|
159
|
+
reason="ok",
|
|
160
|
+
macos_version=macos_ver,
|
|
161
|
+
chip_family=chip,
|
|
162
|
+
framework_check=None,
|
|
163
|
+
xcode_clt=has_clt,
|
|
164
|
+
swiftc=has_swiftc,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
status = query_status()
|
|
169
|
+
except AFMUnavailableError as exc:
|
|
170
|
+
return unavailable(exc.reason)
|
|
171
|
+
|
|
172
|
+
check = str(status.get("check", "unknown"))
|
|
173
|
+
if not status.get("available"):
|
|
174
|
+
detail = status.get("detail") or None
|
|
175
|
+
return unavailable(detail or f"The on-device model is unavailable ({check}).", check)
|
|
176
|
+
|
|
177
|
+
return AFMAvailability(
|
|
178
|
+
available=True,
|
|
179
|
+
model=MODEL_CORE_ADVANCED,
|
|
180
|
+
fallback_model=fallback,
|
|
181
|
+
reason="ok",
|
|
182
|
+
macos_version=macos_ver,
|
|
183
|
+
chip_family=chip,
|
|
184
|
+
framework_check=check,
|
|
185
|
+
xcode_clt=has_clt,
|
|
186
|
+
swiftc=has_swiftc,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def require(*, runtime: bool = True) -> AFMAvailability:
|
|
191
|
+
"""Like probe(), but raises when the models are unavailable."""
|
|
192
|
+
avail = probe(runtime=runtime)
|
|
193
|
+
if not avail.available:
|
|
194
|
+
raise AFMUnavailableError(avail.reason)
|
|
195
|
+
return avail
|