8a66b9086a
- Started from ac75faa (initial E4B-MarkBase integration)
- Kept Sources/ (all engine code) + Package.swift + .gitignore
- Removed all ad-hoc tests, documentation, scripts, Python files
- Added Tests/00_Unit/ (MathTest, TokenizerTest, SamplerTest)
- Added .gitea/workflows/ci.yaml (build + unit tests + lint)
- Added Scripts/check_resources.sh (memory-aware test runner)
- Added Tests/Manifest.json (resource requirements for all tests)
- Focus: 4-bit quantized models only
140 lines
4.5 KiB
Swift
140 lines
4.5 KiB
Swift
import Foundation
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Cross-Device Client
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
/// Cross-device client for making requests to other nodes
|
|
public final class CrossDeviceClient: @unchecked Sendable {
|
|
private let session: URLSession
|
|
private let loadBalancer: LoadBalancer
|
|
private let timeout: TimeInterval
|
|
|
|
public init(
|
|
loadBalancer: LoadBalancer,
|
|
timeout: TimeInterval = 30
|
|
) {
|
|
self.loadBalancer = loadBalancer
|
|
self.timeout = timeout
|
|
|
|
let config = URLSessionConfiguration.default
|
|
config.timeoutIntervalForRequest = timeout
|
|
self.session = URLSession(configuration: config)
|
|
}
|
|
|
|
/// Send request to cluster
|
|
public func sendToCluster(
|
|
endpoint: String,
|
|
method: String = "POST",
|
|
body: Data? = nil
|
|
) async throws -> CrossDeviceResponse {
|
|
guard let node = loadBalancer.getNextNode() else {
|
|
throw CrossDeviceError.noHealthyNodes
|
|
}
|
|
|
|
return try await sendToNode(
|
|
node: node,
|
|
endpoint: endpoint,
|
|
method: method,
|
|
body: body
|
|
)
|
|
}
|
|
|
|
/// Send request to specific node
|
|
public func sendToNode(
|
|
node: DeviceNode,
|
|
endpoint: String,
|
|
method: String = "POST",
|
|
body: Data? = nil
|
|
) async throws -> CrossDeviceResponse {
|
|
let url = URL(string: "\(node.baseURL)\(endpoint)")!
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = method
|
|
request.httpBody = body
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
request.timeoutInterval = timeout
|
|
|
|
let startTime = Date()
|
|
|
|
do {
|
|
let (data, response) = try await session.data(for: request)
|
|
let latency = Date().timeIntervalSince(startTime)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse else {
|
|
throw CrossDeviceError.invalidResponse
|
|
}
|
|
|
|
// Update node status
|
|
loadBalancer.updateNodeStatus(
|
|
id: node.id,
|
|
status: httpResponse.statusCode < 500 ? .healthy : .degraded,
|
|
load: nil
|
|
)
|
|
|
|
return CrossDeviceResponse(
|
|
requestId: UUID().uuidString,
|
|
statusCode: httpResponse.statusCode,
|
|
body: data,
|
|
latency: latency,
|
|
nodeId: node.id
|
|
)
|
|
} catch {
|
|
let latency = Date().timeIntervalSince(startTime)
|
|
|
|
// Update node status
|
|
loadBalancer.updateNodeStatus(id: node.id, status: .unhealthy)
|
|
|
|
throw CrossDeviceError.requestFailed(error)
|
|
}
|
|
}
|
|
|
|
/// Broadcast request to all healthy nodes
|
|
public func broadcastToAll(
|
|
endpoint: String,
|
|
method: String = "POST",
|
|
body: Data? = nil
|
|
) async throws -> [CrossDeviceResponse] {
|
|
let nodes = loadBalancer.getNodes().filter { $0.status == .healthy }
|
|
|
|
return try await withThrowingTaskGroup(of: CrossDeviceResponse.self) { group in
|
|
for node in nodes {
|
|
group.addTask {
|
|
try await self.sendToNode(
|
|
node: node,
|
|
endpoint: endpoint,
|
|
method: method,
|
|
body: body
|
|
)
|
|
}
|
|
}
|
|
|
|
var responses: [CrossDeviceResponse] = []
|
|
for try await response in group {
|
|
responses.append(response)
|
|
}
|
|
return responses
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Cross-device errors
|
|
public enum CrossDeviceError: Error, LocalizedError {
|
|
case noHealthyNodes
|
|
case invalidResponse
|
|
case requestFailed(Error)
|
|
case timeout
|
|
|
|
public var errorDescription: String? {
|
|
switch self {
|
|
case .noHealthyNodes:
|
|
return "No healthy nodes available"
|
|
case .invalidResponse:
|
|
return "Invalid response from node"
|
|
case .requestFailed(let error):
|
|
return "Request failed: \(error.localizedDescription)"
|
|
case .timeout:
|
|
return "Request timed out"
|
|
}
|
|
}
|
|
}
|