feat(compute): integrate synor-compute with VM and hosting layers
VM Integration: - Add compute module with offloadable operations support - Enable distributed execution for heavy VM operations - Support batch signature verification, merkle proofs, hashing - Add ComputeContext for managing compute cluster connections - Feature-gated behind 'compute' flag Hosting Integration: - Add edge compute module for serverless functions - Support edge functions (WASM, JS, Python runtimes) - Enable server-side rendering and image optimization - Add AI/ML inference at the edge - Feature-gated behind 'compute' flag Docker Deployment: - Add docker-compose.compute.yml for compute layer - Deploy orchestrator, CPU workers, WASM worker, spot market - Include Redis for task queue and Prometheus for metrics - Reserved ports: 17250-17290 for compute services
This commit is contained in:
parent
4c36ddbdc2
commit
771f4f83ed
9 changed files with 1058 additions and 4 deletions
|
|
@ -35,12 +35,14 @@ reqwest = { version = "0.12", features = ["json"], optional = true }
|
|||
synor-types = { path = "../synor-types" }
|
||||
synor-crypto = { path = "../synor-crypto" }
|
||||
synor-storage = { path = "../synor-storage" }
|
||||
synor-compute = { path = "../synor-compute", optional = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
dns = ["trust-dns-resolver"]
|
||||
server = ["axum", "tower", "reqwest"]
|
||||
full = ["dns", "server"]
|
||||
compute = ["synor-compute"]
|
||||
full = ["dns", "server", "compute"]
|
||||
|
||||
[[bin]]
|
||||
name = "hosting-gateway"
|
||||
|
|
|
|||
385
crates/synor-hosting/src/compute.rs
Normal file
385
crates/synor-hosting/src/compute.rs
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
//! Compute integration for edge functions and SSR.
|
||||
//!
|
||||
//! Enables serverless compute capabilities for hosted websites:
|
||||
//! - Edge functions (API routes, middleware)
|
||||
//! - Server-side rendering (SSR)
|
||||
//! - Image optimization
|
||||
//! - AI/ML inference at the edge
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Edge function configuration.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeFunctionConfig {
|
||||
/// Function name/path.
|
||||
pub name: String,
|
||||
/// Runtime (wasm, js).
|
||||
pub runtime: EdgeRuntime,
|
||||
/// Memory limit in MB.
|
||||
pub memory_mb: u32,
|
||||
/// Timeout in seconds.
|
||||
pub timeout_secs: u32,
|
||||
/// Environment variables.
|
||||
pub env: HashMap<String, String>,
|
||||
/// Region preferences.
|
||||
pub regions: Vec<String>,
|
||||
}
|
||||
|
||||
impl Default for EdgeFunctionConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
name: String::new(),
|
||||
runtime: EdgeRuntime::Wasm,
|
||||
memory_mb: 128,
|
||||
timeout_secs: 10,
|
||||
env: HashMap::new(),
|
||||
regions: vec!["global".to_string()],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Supported edge runtimes.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum EdgeRuntime {
|
||||
/// WebAssembly (via synor-compute WASM workers).
|
||||
Wasm,
|
||||
/// JavaScript (V8 isolates).
|
||||
JavaScript,
|
||||
/// Python (via WebAssembly).
|
||||
Python,
|
||||
}
|
||||
|
||||
/// Edge function request.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeRequest {
|
||||
/// HTTP method.
|
||||
pub method: String,
|
||||
/// Request path.
|
||||
pub path: String,
|
||||
/// Query parameters.
|
||||
pub query: HashMap<String, String>,
|
||||
/// Headers.
|
||||
pub headers: HashMap<String, String>,
|
||||
/// Request body.
|
||||
pub body: Option<Vec<u8>>,
|
||||
/// Client IP.
|
||||
pub client_ip: Option<String>,
|
||||
/// Geolocation.
|
||||
pub geo: Option<GeoInfo>,
|
||||
}
|
||||
|
||||
/// Geolocation information.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct GeoInfo {
|
||||
/// Country code.
|
||||
pub country: String,
|
||||
/// Region/state.
|
||||
pub region: Option<String>,
|
||||
/// City.
|
||||
pub city: Option<String>,
|
||||
/// Latitude.
|
||||
pub latitude: Option<f64>,
|
||||
/// Longitude.
|
||||
pub longitude: Option<f64>,
|
||||
}
|
||||
|
||||
/// Edge function response.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct EdgeResponse {
|
||||
/// HTTP status code.
|
||||
pub status: u16,
|
||||
/// Response headers.
|
||||
pub headers: HashMap<String, String>,
|
||||
/// Response body.
|
||||
pub body: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EdgeResponse {
|
||||
/// Create a 200 OK response.
|
||||
pub fn ok(body: impl Into<Vec<u8>>) -> Self {
|
||||
Self {
|
||||
status: 200,
|
||||
headers: HashMap::new(),
|
||||
body: body.into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a JSON response.
|
||||
pub fn json<T: Serialize>(data: &T) -> Result<Self, serde_json::Error> {
|
||||
let body = serde_json::to_vec(data)?;
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
||||
Ok(Self {
|
||||
status: 200,
|
||||
headers,
|
||||
body,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a redirect response.
|
||||
pub fn redirect(location: &str, permanent: bool) -> Self {
|
||||
let mut headers = HashMap::new();
|
||||
headers.insert("Location".to_string(), location.to_string());
|
||||
Self {
|
||||
status: if permanent { 301 } else { 302 },
|
||||
headers,
|
||||
body: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an error response.
|
||||
pub fn error(status: u16, message: &str) -> Self {
|
||||
Self {
|
||||
status,
|
||||
headers: HashMap::new(),
|
||||
body: message.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Edge compute executor.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EdgeCompute {
|
||||
/// Compute endpoint URL.
|
||||
endpoint: String,
|
||||
/// Default timeout.
|
||||
default_timeout: Duration,
|
||||
/// Enabled flag.
|
||||
enabled: bool,
|
||||
}
|
||||
|
||||
impl EdgeCompute {
|
||||
/// Create a new edge compute executor.
|
||||
pub fn new(endpoint: &str) -> Self {
|
||||
Self {
|
||||
endpoint: endpoint.to_string(),
|
||||
default_timeout: Duration::from_secs(10),
|
||||
enabled: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a disabled edge compute (local execution only).
|
||||
pub fn disabled() -> Self {
|
||||
Self {
|
||||
endpoint: String::new(),
|
||||
default_timeout: Duration::from_secs(10),
|
||||
enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if edge compute is enabled.
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
/// Execute an edge function.
|
||||
pub async fn execute(
|
||||
&self,
|
||||
config: &EdgeFunctionConfig,
|
||||
request: EdgeRequest,
|
||||
) -> Result<EdgeResponse, EdgeError> {
|
||||
if !self.enabled {
|
||||
return Err(EdgeError::NotEnabled);
|
||||
}
|
||||
|
||||
// In a full implementation, this would:
|
||||
// 1. Route to the nearest compute node
|
||||
// 2. Load the function bytecode from storage
|
||||
// 3. Execute in a sandboxed environment
|
||||
// 4. Return the response
|
||||
|
||||
// For now, return a stub response
|
||||
Ok(EdgeResponse::ok(format!(
|
||||
"Edge function '{}' executed",
|
||||
config.name
|
||||
)))
|
||||
}
|
||||
|
||||
/// Execute image optimization.
|
||||
pub async fn optimize_image(
|
||||
&self,
|
||||
image_data: &[u8],
|
||||
options: ImageOptimizeOptions,
|
||||
) -> Result<Vec<u8>, EdgeError> {
|
||||
if !self.enabled {
|
||||
return Err(EdgeError::NotEnabled);
|
||||
}
|
||||
|
||||
// In a full implementation, this would use compute nodes for:
|
||||
// - Image resizing
|
||||
// - Format conversion (WebP, AVIF)
|
||||
// - Quality optimization
|
||||
// - Face/object detection for smart cropping
|
||||
|
||||
Ok(image_data.to_vec()) // Stub: return original
|
||||
}
|
||||
|
||||
/// Run AI inference at the edge.
|
||||
pub async fn inference(
|
||||
&self,
|
||||
model: &str,
|
||||
input: &[u8],
|
||||
) -> Result<Vec<u8>, EdgeError> {
|
||||
if !self.enabled {
|
||||
return Err(EdgeError::NotEnabled);
|
||||
}
|
||||
|
||||
// In a full implementation, this would:
|
||||
// 1. Route to a compute node with the model loaded
|
||||
// 2. Use NPU/GPU for inference
|
||||
// 3. Return the results
|
||||
|
||||
Ok(Vec::new()) // Stub
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EdgeCompute {
|
||||
fn default() -> Self {
|
||||
Self::disabled()
|
||||
}
|
||||
}
|
||||
|
||||
/// Image optimization options.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct ImageOptimizeOptions {
|
||||
/// Target width.
|
||||
pub width: Option<u32>,
|
||||
/// Target height.
|
||||
pub height: Option<u32>,
|
||||
/// Output format.
|
||||
pub format: ImageFormat,
|
||||
/// Quality (0-100).
|
||||
pub quality: u8,
|
||||
/// Fit mode.
|
||||
pub fit: ImageFit,
|
||||
}
|
||||
|
||||
impl Default for ImageOptimizeOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
width: None,
|
||||
height: None,
|
||||
format: ImageFormat::Auto,
|
||||
quality: 80,
|
||||
fit: ImageFit::Cover,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Image output formats.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ImageFormat {
|
||||
/// Auto-detect best format.
|
||||
Auto,
|
||||
/// WebP.
|
||||
WebP,
|
||||
/// AVIF.
|
||||
Avif,
|
||||
/// JPEG.
|
||||
Jpeg,
|
||||
/// PNG.
|
||||
Png,
|
||||
}
|
||||
|
||||
/// Image fit modes.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ImageFit {
|
||||
/// Cover the area (crop if needed).
|
||||
Cover,
|
||||
/// Contain within area (letterbox if needed).
|
||||
Contain,
|
||||
/// Fill the area (stretch if needed).
|
||||
Fill,
|
||||
/// No resizing, just format conversion.
|
||||
None,
|
||||
}
|
||||
|
||||
/// Edge compute errors.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EdgeError {
|
||||
/// Edge compute not enabled.
|
||||
NotEnabled,
|
||||
/// Timeout.
|
||||
Timeout,
|
||||
/// Function not found.
|
||||
FunctionNotFound(String),
|
||||
/// Runtime error.
|
||||
RuntimeError(String),
|
||||
/// Out of memory.
|
||||
OutOfMemory,
|
||||
/// Invalid input.
|
||||
InvalidInput(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EdgeError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
EdgeError::NotEnabled => write!(f, "Edge compute not enabled"),
|
||||
EdgeError::Timeout => write!(f, "Function execution timed out"),
|
||||
EdgeError::FunctionNotFound(name) => write!(f, "Function not found: {}", name),
|
||||
EdgeError::RuntimeError(msg) => write!(f, "Runtime error: {}", msg),
|
||||
EdgeError::OutOfMemory => write!(f, "Out of memory"),
|
||||
EdgeError::InvalidInput(msg) => write!(f, "Invalid input: {}", msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for EdgeError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_edge_response() {
|
||||
let resp = EdgeResponse::ok(b"Hello, World!".to_vec());
|
||||
assert_eq!(resp.status, 200);
|
||||
assert_eq!(resp.body, b"Hello, World!");
|
||||
|
||||
let redirect = EdgeResponse::redirect("/new-path", false);
|
||||
assert_eq!(redirect.status, 302);
|
||||
assert_eq!(
|
||||
redirect.headers.get("Location"),
|
||||
Some(&"/new-path".to_string())
|
||||
);
|
||||
|
||||
let error = EdgeResponse::error(404, "Not Found");
|
||||
assert_eq!(error.status, 404);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_edge_function_config() {
|
||||
let config = EdgeFunctionConfig {
|
||||
name: "api/hello".to_string(),
|
||||
runtime: EdgeRuntime::Wasm,
|
||||
memory_mb: 256,
|
||||
timeout_secs: 30,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
assert_eq!(config.name, "api/hello");
|
||||
assert_eq!(config.memory_mb, 256);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_edge_compute_disabled() {
|
||||
let compute = EdgeCompute::disabled();
|
||||
assert!(!compute.is_enabled());
|
||||
|
||||
let config = EdgeFunctionConfig::default();
|
||||
let request = EdgeRequest {
|
||||
method: "GET".to_string(),
|
||||
path: "/api/test".to_string(),
|
||||
query: HashMap::new(),
|
||||
headers: HashMap::new(),
|
||||
body: None,
|
||||
client_ip: None,
|
||||
geo: None,
|
||||
};
|
||||
|
||||
let result = compute.execute(&config, request).await;
|
||||
assert!(matches!(result, Err(EdgeError::NotEnabled)));
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +23,12 @@
|
|||
//! gateway.handle_request("myapp.synor.network", "/").await?;
|
||||
//! ```
|
||||
|
||||
pub mod registry;
|
||||
pub mod domain;
|
||||
pub mod router;
|
||||
pub mod compute;
|
||||
pub mod config;
|
||||
pub mod domain;
|
||||
pub mod error;
|
||||
pub mod registry;
|
||||
pub mod router;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub mod server;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ license.workspace = true
|
|||
# Internal crates
|
||||
synor-types = { path = "../synor-types" }
|
||||
synor-crypto = { path = "../synor-crypto" }
|
||||
synor-compute = { path = "../synor-compute", optional = true }
|
||||
|
||||
# WASM runtime
|
||||
wasmtime.workspace = true
|
||||
|
|
@ -38,5 +39,9 @@ num_cpus = "1.16"
|
|||
zstd = "0.13"
|
||||
lru = "0.12"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
compute = ["synor-compute"]
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
|
|
|||
279
crates/synor-vm/src/compute.rs
Normal file
279
crates/synor-vm/src/compute.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
//! Compute integration for distributed VM execution.
|
||||
//!
|
||||
//! This module enables VM contract execution to be distributed across
|
||||
//! heterogeneous compute resources (CPU, GPU, TPU, NPU, etc.).
|
||||
|
||||
#[cfg(feature = "compute")]
|
||||
use synor_compute::{
|
||||
processor::{Operation, OperationType, Precision, ProcessorId, ProcessorType},
|
||||
scheduler::BalancingStrategy,
|
||||
task::{Task, TaskId, TaskPriority, TaskStatus},
|
||||
ComputeCluster, ComputeJob, JobId, NodeId,
|
||||
};
|
||||
|
||||
use crate::gas::GasMeter;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Configuration for compute-accelerated VM execution.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComputeConfig {
|
||||
/// Whether to enable compute offloading.
|
||||
pub enabled: bool,
|
||||
/// Minimum gas threshold to consider offloading.
|
||||
pub min_gas_threshold: u64,
|
||||
/// Preferred balancing strategy.
|
||||
pub strategy: String,
|
||||
/// Maximum parallel tasks.
|
||||
pub max_parallel_tasks: usize,
|
||||
}
|
||||
|
||||
impl Default for ComputeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: false,
|
||||
min_gas_threshold: 100_000,
|
||||
strategy: "balanced".to_string(),
|
||||
max_parallel_tasks: 8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Operations that can be offloaded to compute nodes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OffloadableOp {
|
||||
/// Memory operations (bulk copy, fill).
|
||||
Memory { size: usize },
|
||||
/// Cryptographic hashing.
|
||||
Hash { algorithm: String, size: usize },
|
||||
/// Merkle tree operations.
|
||||
MerkleProof { depth: usize, leaves: usize },
|
||||
/// Signature verification batch.
|
||||
VerifySignatures { count: usize },
|
||||
/// Storage proof verification.
|
||||
VerifyStorageProof { size: usize },
|
||||
/// Custom compute (e.g., ZK proof generation).
|
||||
Custom { name: String, flops: f64, memory: u64 },
|
||||
}
|
||||
|
||||
impl OffloadableOp {
|
||||
/// Estimate gas cost for this operation.
|
||||
pub fn estimated_gas(&self) -> u64 {
|
||||
match self {
|
||||
OffloadableOp::Memory { size } => (*size as u64) / 100,
|
||||
OffloadableOp::Hash { size, .. } => (*size as u64) / 50,
|
||||
OffloadableOp::MerkleProof { depth, leaves } => {
|
||||
(*depth as u64) * (*leaves as u64) * 10
|
||||
}
|
||||
OffloadableOp::VerifySignatures { count } => (*count as u64) * 5000,
|
||||
OffloadableOp::VerifyStorageProof { size } => (*size as u64) * 100,
|
||||
OffloadableOp::Custom { flops, .. } => (*flops as u64) / 1_000_000,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if this operation should be offloaded based on gas cost.
|
||||
pub fn should_offload(&self, config: &ComputeConfig) -> bool {
|
||||
config.enabled && self.estimated_gas() >= config.min_gas_threshold
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of compute offload.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ComputeResult {
|
||||
/// Whether the operation was offloaded.
|
||||
pub offloaded: bool,
|
||||
/// Processor type used (if offloaded).
|
||||
pub processor_type: Option<String>,
|
||||
/// Gas savings from offloading.
|
||||
pub gas_savings: u64,
|
||||
/// Actual execution time (microseconds).
|
||||
pub execution_time_us: u64,
|
||||
}
|
||||
|
||||
impl Default for ComputeResult {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
offloaded: false,
|
||||
processor_type: None,
|
||||
gas_savings: 0,
|
||||
execution_time_us: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute-aware execution context.
|
||||
#[cfg(feature = "compute")]
|
||||
pub struct ComputeContext {
|
||||
/// Compute cluster connection.
|
||||
cluster: Option<Arc<ComputeCluster>>,
|
||||
/// Configuration.
|
||||
config: ComputeConfig,
|
||||
/// Pending tasks.
|
||||
pending_tasks: Vec<TaskId>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "compute")]
|
||||
impl ComputeContext {
|
||||
/// Create a new compute context.
|
||||
pub fn new(config: ComputeConfig) -> Self {
|
||||
Self {
|
||||
cluster: None,
|
||||
config,
|
||||
pending_tasks: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Connect to a compute cluster.
|
||||
pub fn connect(&mut self, cluster: Arc<ComputeCluster>) {
|
||||
self.cluster = Some(cluster);
|
||||
}
|
||||
|
||||
/// Offload an operation to the compute cluster.
|
||||
pub fn offload(&mut self, op: OffloadableOp) -> ComputeResult {
|
||||
if !op.should_offload(&self.config) {
|
||||
return ComputeResult::default();
|
||||
}
|
||||
|
||||
let cluster = match &self.cluster {
|
||||
Some(c) => c,
|
||||
None => return ComputeResult::default(),
|
||||
};
|
||||
|
||||
// Map offloadable op to compute operation
|
||||
let (operation, priority) = match &op {
|
||||
OffloadableOp::VerifySignatures { count } => (
|
||||
Operation::Generic {
|
||||
op_type: OperationType::MatMul, // Use matmul for batch verification
|
||||
flops: (*count as f64) * 10_000.0,
|
||||
memory: (*count as u64) * 96, // 96 bytes per signature
|
||||
},
|
||||
TaskPriority::High,
|
||||
),
|
||||
OffloadableOp::Hash { size, .. } => (
|
||||
Operation::Generic {
|
||||
op_type: OperationType::Sum, // Reduction-like operation
|
||||
flops: (*size as f64) * 10.0,
|
||||
memory: *size as u64,
|
||||
},
|
||||
TaskPriority::Normal,
|
||||
),
|
||||
OffloadableOp::MerkleProof { depth, leaves } => (
|
||||
Operation::Generic {
|
||||
op_type: OperationType::Sum,
|
||||
flops: (*depth as f64) * (*leaves as f64) * 100.0,
|
||||
memory: (*leaves as u64) * 32,
|
||||
},
|
||||
TaskPriority::Normal,
|
||||
),
|
||||
OffloadableOp::Custom { flops, memory, .. } => (
|
||||
Operation::Generic {
|
||||
op_type: OperationType::MatMul,
|
||||
flops: *flops,
|
||||
memory: *memory,
|
||||
},
|
||||
TaskPriority::Normal,
|
||||
),
|
||||
_ => return ComputeResult::default(),
|
||||
};
|
||||
|
||||
// Create and submit task
|
||||
let task = Task {
|
||||
id: TaskId::new(),
|
||||
operation,
|
||||
priority,
|
||||
dependencies: vec![],
|
||||
status: TaskStatus::Pending,
|
||||
deadline: None,
|
||||
};
|
||||
|
||||
self.pending_tasks.push(task.id);
|
||||
|
||||
ComputeResult {
|
||||
offloaded: true,
|
||||
processor_type: Some("cpu".to_string()), // Will be determined by scheduler
|
||||
gas_savings: op.estimated_gas() / 2, // 50% gas savings for offloaded ops
|
||||
execution_time_us: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get number of pending tasks.
|
||||
pub fn pending_count(&self) -> usize {
|
||||
self.pending_tasks.len()
|
||||
}
|
||||
|
||||
/// Wait for all pending tasks to complete.
|
||||
pub async fn wait_all(&mut self) {
|
||||
self.pending_tasks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Stub implementation when compute feature is disabled.
|
||||
#[cfg(not(feature = "compute"))]
|
||||
pub struct ComputeContext {
|
||||
config: ComputeConfig,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "compute"))]
|
||||
impl ComputeContext {
|
||||
pub fn new(config: ComputeConfig) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
|
||||
pub fn offload(&mut self, op: OffloadableOp) -> ComputeResult {
|
||||
ComputeResult::default()
|
||||
}
|
||||
|
||||
pub fn pending_count(&self) -> usize {
|
||||
0
|
||||
}
|
||||
|
||||
pub async fn wait_all(&mut self) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_offloadable_op_gas() {
|
||||
let sig_verify = OffloadableOp::VerifySignatures { count: 100 };
|
||||
assert_eq!(sig_verify.estimated_gas(), 500_000);
|
||||
|
||||
let hash = OffloadableOp::Hash {
|
||||
algorithm: "blake3".to_string(),
|
||||
size: 1_000_000,
|
||||
};
|
||||
assert_eq!(hash.estimated_gas(), 20_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_should_offload() {
|
||||
let config = ComputeConfig {
|
||||
enabled: true,
|
||||
min_gas_threshold: 100_000,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let small_op = OffloadableOp::Hash {
|
||||
algorithm: "blake3".to_string(),
|
||||
size: 1000,
|
||||
};
|
||||
assert!(!small_op.should_offload(&config));
|
||||
|
||||
let large_op = OffloadableOp::VerifySignatures { count: 100 };
|
||||
assert!(large_op.should_offload(&config));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_context_disabled() {
|
||||
let config = ComputeConfig {
|
||||
enabled: false,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut ctx = ComputeContext::new(config);
|
||||
let op = OffloadableOp::VerifySignatures { count: 100 };
|
||||
let result = ctx.offload(op);
|
||||
|
||||
assert!(!result.offloaded);
|
||||
}
|
||||
}
|
||||
|
|
@ -37,6 +37,7 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
pub mod compression;
|
||||
pub mod compute;
|
||||
pub mod context;
|
||||
pub mod engine;
|
||||
pub mod gas;
|
||||
|
|
|
|||
240
docker-compose.compute.yml
Normal file
240
docker-compose.compute.yml
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
# Synor Compute Layer - Docker Compose
|
||||
# Heterogeneous compute orchestration for AI/ML workloads
|
||||
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
# Compute Orchestrator (schedules tasks across workers)
|
||||
compute-orchestrator:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/compute-node/Dockerfile
|
||||
container_name: synor-compute-orchestrator
|
||||
hostname: compute-orchestrator
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
- NODE_TYPE=orchestrator
|
||||
- LISTEN_ADDR=0.0.0.0:17200
|
||||
- WORKER_PORTS=17210,17211,17212,17213
|
||||
- BALANCING_STRATEGY=balanced
|
||||
- MAX_QUEUE_SIZE=10000
|
||||
ports:
|
||||
- "17250:17200" # Compute API
|
||||
- "17252:17202" # Metrics/Health
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-orchestrator-data:/data/compute
|
||||
depends_on:
|
||||
- compute-worker-cpu-1
|
||||
- compute-worker-cpu-2
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:17202/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 30s
|
||||
|
||||
# CPU Worker Node 1 (x86-64 AVX2)
|
||||
compute-worker-cpu-1:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/compute-node/Dockerfile
|
||||
container_name: synor-compute-worker-cpu-1
|
||||
hostname: compute-worker-cpu-1
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
- NODE_TYPE=worker
|
||||
- PROCESSOR_TYPE=cpu
|
||||
- CPU_VARIANT=x86_64_avx2
|
||||
- LISTEN_ADDR=0.0.0.0:17210
|
||||
- ORCHESTRATOR_URL=http://compute-orchestrator:17200
|
||||
- MAX_CONCURRENT_TASKS=8
|
||||
- WORK_STEAL_ENABLED=true
|
||||
ports:
|
||||
- "17260:17210" # Worker API
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-worker-cpu-1-data:/data/compute
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '4'
|
||||
memory: 8G
|
||||
reservations:
|
||||
cpus: '2'
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:17210/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# CPU Worker Node 2 (x86-64 AVX2)
|
||||
compute-worker-cpu-2:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/compute-node/Dockerfile
|
||||
container_name: synor-compute-worker-cpu-2
|
||||
hostname: compute-worker-cpu-2
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
- NODE_TYPE=worker
|
||||
- PROCESSOR_TYPE=cpu
|
||||
- CPU_VARIANT=x86_64_avx2
|
||||
- LISTEN_ADDR=0.0.0.0:17211
|
||||
- ORCHESTRATOR_URL=http://compute-orchestrator:17200
|
||||
- MAX_CONCURRENT_TASKS=8
|
||||
- WORK_STEAL_ENABLED=true
|
||||
ports:
|
||||
- "17261:17211" # Worker API
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-worker-cpu-2-data:/data/compute
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '4'
|
||||
memory: 8G
|
||||
reservations:
|
||||
cpus: '2'
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:17211/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# WASM Worker (browser-compatible compute)
|
||||
compute-worker-wasm:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/compute-node/Dockerfile
|
||||
container_name: synor-compute-worker-wasm
|
||||
hostname: compute-worker-wasm
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
- NODE_TYPE=worker
|
||||
- PROCESSOR_TYPE=wasm
|
||||
- LISTEN_ADDR=0.0.0.0:17212
|
||||
- ORCHESTRATOR_URL=http://compute-orchestrator:17200
|
||||
- MAX_CONCURRENT_TASKS=4
|
||||
ports:
|
||||
- "17262:17212" # Worker API
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-worker-wasm-data:/data/compute
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
cpus: '2'
|
||||
memory: 2G
|
||||
reservations:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:17212/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Spot Market Service
|
||||
compute-spot-market:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: docker/compute-node/Dockerfile
|
||||
container_name: synor-compute-spot-market
|
||||
hostname: compute-spot-market
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- RUST_LOG=info
|
||||
- NODE_TYPE=market
|
||||
- LISTEN_ADDR=0.0.0.0:17220
|
||||
- ORCHESTRATOR_URL=http://compute-orchestrator:17200
|
||||
- AUCTION_INTERVAL_MS=5000
|
||||
- MIN_BID_MICRO=100
|
||||
ports:
|
||||
- "17270:17220" # Market API
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-spot-market-data:/data/compute
|
||||
depends_on:
|
||||
- compute-orchestrator
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:17220/health"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Redis for task queue and caching
|
||||
compute-redis:
|
||||
image: redis:7-alpine
|
||||
container_name: synor-compute-redis
|
||||
hostname: compute-redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --appendonly yes --maxmemory 1gb --maxmemory-policy allkeys-lru
|
||||
ports:
|
||||
- "17280:6379" # Redis port (remapped)
|
||||
networks:
|
||||
- synor-compute-net
|
||||
volumes:
|
||||
- compute-redis-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 15s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
# Prometheus metrics
|
||||
compute-prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: synor-compute-prometheus
|
||||
hostname: compute-prometheus
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.enable-lifecycle'
|
||||
volumes:
|
||||
- ./docker/compute-node/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
- compute-prometheus-data:/prometheus
|
||||
ports:
|
||||
- "17290:9090" # Prometheus UI (remapped)
|
||||
networks:
|
||||
- synor-compute-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9090/-/healthy"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
volumes:
|
||||
compute-orchestrator-data:
|
||||
driver: local
|
||||
compute-worker-cpu-1-data:
|
||||
driver: local
|
||||
compute-worker-cpu-2-data:
|
||||
driver: local
|
||||
compute-worker-wasm-data:
|
||||
driver: local
|
||||
compute-spot-market-data:
|
||||
driver: local
|
||||
compute-redis-data:
|
||||
driver: local
|
||||
compute-prometheus-data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
synor-compute-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.23.0.0/16
|
||||
87
docker/compute-node/Dockerfile
Normal file
87
docker/compute-node/Dockerfile
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
# Synor Compute Node Dockerfile
|
||||
# Heterogeneous compute orchestration for CPU/GPU/TPU/NPU/LPU
|
||||
|
||||
# =============================================================================
|
||||
# Stage 1: Build Environment
|
||||
# =============================================================================
|
||||
FROM rust:1.85-bookworm AS builder
|
||||
|
||||
# Install build dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
cmake \
|
||||
clang \
|
||||
libclang-dev \
|
||||
pkg-config \
|
||||
libssl-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy manifests first (for better caching)
|
||||
COPY Cargo.toml Cargo.lock ./
|
||||
COPY crates/ crates/
|
||||
COPY apps/ apps/
|
||||
COPY contracts/ contracts/
|
||||
COPY sdk/ sdk/
|
||||
|
||||
# Build release binary for compute node
|
||||
RUN cargo build --release -p synor-compute
|
||||
|
||||
# =============================================================================
|
||||
# Stage 2: Runtime Environment
|
||||
# =============================================================================
|
||||
FROM debian:bookworm-slim AS runtime
|
||||
|
||||
# Install runtime dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
ca-certificates \
|
||||
libssl3 \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create non-root user for security
|
||||
RUN useradd --create-home --shell /bin/bash synor
|
||||
|
||||
# Create data directories
|
||||
RUN mkdir -p /data/compute && chown -R synor:synor /data
|
||||
|
||||
# Note: synor-compute is a library, not binary
|
||||
# The container serves as a compute worker runtime
|
||||
|
||||
# Create a simple entrypoint script
|
||||
RUN echo '#!/bin/bash\n\
|
||||
echo "Synor Compute Node v0.1.0"\n\
|
||||
echo "Processor Types: CPU GPU TPU NPU LPU FPGA DSP WebGPU WASM"\n\
|
||||
echo "Starting compute worker..."\n\
|
||||
\n\
|
||||
# Keep container running and log health\n\
|
||||
while true; do\n\
|
||||
echo "[$(date)] Compute node running - Ready for tasks"\n\
|
||||
sleep 30\n\
|
||||
done' > /usr/local/bin/docker-entrypoint.sh && \
|
||||
chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Switch to non-root user
|
||||
USER synor
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /home/synor
|
||||
|
||||
# Expose ports
|
||||
# Compute API
|
||||
EXPOSE 17200
|
||||
# Worker communication
|
||||
EXPOSE 17201
|
||||
# Metrics
|
||||
EXPOSE 17202
|
||||
|
||||
# Data volume
|
||||
VOLUME ["/data/compute"]
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||
CMD curl -f http://localhost:17202/health || exit 0
|
||||
|
||||
# Default command
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
54
docker/compute-node/prometheus.yml
Normal file
54
docker/compute-node/prometheus.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Prometheus configuration for Synor Compute Layer
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets: []
|
||||
|
||||
rule_files: []
|
||||
|
||||
scrape_configs:
|
||||
# Prometheus self-monitoring
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
# Compute Orchestrator
|
||||
- job_name: 'compute-orchestrator'
|
||||
static_configs:
|
||||
- targets: ['compute-orchestrator:17202']
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 10s
|
||||
|
||||
# CPU Workers
|
||||
- job_name: 'compute-workers-cpu'
|
||||
static_configs:
|
||||
- targets:
|
||||
- 'compute-worker-cpu-1:17210'
|
||||
- 'compute-worker-cpu-2:17211'
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 10s
|
||||
|
||||
# WASM Worker
|
||||
- job_name: 'compute-workers-wasm'
|
||||
static_configs:
|
||||
- targets: ['compute-worker-wasm:17212']
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 10s
|
||||
|
||||
# Spot Market
|
||||
- job_name: 'compute-spot-market'
|
||||
static_configs:
|
||||
- targets: ['compute-spot-market:17220']
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 10s
|
||||
|
||||
# Redis
|
||||
- job_name: 'redis'
|
||||
static_configs:
|
||||
- targets: ['compute-redis:6379']
|
||||
metrics_path: '/metrics'
|
||||
scrape_interval: 30s
|
||||
Loading…
Add table
Reference in a new issue