v2: Initial clean branch with unit tests + CI/CD pipeline
- 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
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user