schemas.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. """Shared Pydantic schemas for Fabric control plane and node-agent."""
  2. from __future__ import annotations
  3. from enum import Enum
  4. from pydantic import BaseModel, Field
  5. from typing import Optional
  6. import time
  7. class PriorityClass(str, Enum):
  8. low = "low"
  9. normal = "normal"
  10. high = "high"
  11. critical = "critical"
  12. class ReclaimStrategy(str, Enum):
  13. process_kill = "process-kill"
  14. vllm_sleep = "vllm-sleep"
  15. cuda_checkpoint = "cuda-checkpoint"
  16. none = "none"
  17. class ModuleState(str, Enum):
  18. loaded = "loaded"
  19. running = "running"
  20. idle = "idle"
  21. sleeping = "sleeping"
  22. unloaded = "unloaded"
  23. error = "error"
  24. warming = "warming"
  25. class RuntimeType(str, Enum):
  26. systemd_wrap = "systemd-wrap"
  27. subprocess = "subprocess"
  28. vllm = "vllm"
  29. python_class = "python-class"
  30. comfy_graph = "comfy-graph"
  31. proc_exec = "proc-exec"
  32. class GpuResources(BaseModel):
  33. gpus: int = 1
  34. vram_mb: int | str = "auto"
  35. class LifecycleConfig(BaseModel):
  36. reclaim: ReclaimStrategy = ReclaimStrategy.process_kill
  37. idle_evict_s: int = 300
  38. warm_default: bool = False
  39. idle_evict_s: int = 300
  40. warm_default: bool = False
  41. warmup_s: int = 10
  42. class SchedulingConfig(BaseModel):
  43. priority_class: PriorityClass = PriorityClass.normal
  44. min_replicas: int = 0
  45. max_replicas: int = 8
  46. batch: bool = False
  47. class SystemdWrapConfig(BaseModel):
  48. systemd_unit: str
  49. health_url: str | None = None
  50. health_timeout_s: int = 120
  51. infer_url: str
  52. infer_method: str = "POST"
  53. class ModuleManifest(BaseModel):
  54. name: str
  55. description: str = ""
  56. version: str = "1.0"
  57. modality: str = "general"
  58. aliases: list[str] = Field(default_factory=list)
  59. runtime: RuntimeType = RuntimeType.systemd_wrap
  60. resources: GpuResources = Field(default_factory=GpuResources)
  61. lifecycle: LifecycleConfig = Field(default_factory=LifecycleConfig)
  62. scheduling: SchedulingConfig = Field(default_factory=SchedulingConfig)
  63. systemd: SystemdWrapConfig | None = None
  64. capabilities: list[str] = Field(default_factory=list)
  65. class GpuPidUsage(BaseModel):
  66. gpu_index: int
  67. pid: int
  68. vram_mb: int
  69. process_name: str = ""
  70. class GpuMemInfo(BaseModel):
  71. """Per-physical-GPU memory capacity + telemetry (additive). One entry per
  72. physical GPU (counted once, NOT per process). Absent fields degrade to
  73. 0 / None so old and new components interoperate."""
  74. gpu_index: int = 0
  75. name: str = ""
  76. mem_total_mb: int = 0
  77. mem_used_mb: int = 0
  78. mem_free_mb: int = 0
  79. util_pct: Optional[int] = None
  80. temp_c: Optional[int] = None
  81. power_w: Optional[float] = None
  82. class ModuleStatus(BaseModel):
  83. name: str
  84. state: ModuleState
  85. vram_mb: int = 0
  86. required_vram_mb: int = 0 # declared manifest requirement (0 = auto/unknown)
  87. last_infer_at: float | None = None
  88. health_ok: bool = False
  89. infer_url: str = ""
  90. modality: str = "general"
  91. aliases: list[str] = Field(default_factory=list)
  92. priority_class: PriorityClass = PriorityClass.normal
  93. reclaim: ReclaimStrategy = ReclaimStrategy.process_kill
  94. idle_evict_s: int = 300
  95. warm_default: bool = False
  96. class Heartbeat(BaseModel):
  97. node_id: str
  98. node_ip: str
  99. agent_port: int = 7701
  100. hypervisor: str = "unknown"
  101. gpu_status: list[GpuPidUsage] = Field(default_factory=list)
  102. modules: list[ModuleStatus] = Field(default_factory=list)
  103. uptime_s: int = 0
  104. timestamp: float = Field(default_factory=time.time)
  105. # --- additive VRAM-capacity + telemetry (per physical GPU) ---
  106. gpus: list[GpuMemInfo] = Field(default_factory=list)
  107. vram_total_mb: int = 0
  108. vram_free_mb: int = 0
  109. driver_version: str = ""
  110. class NodeInfo(BaseModel):
  111. node_id: str
  112. node_ip: str
  113. agent_port: int = 7701
  114. hypervisor: str = "unknown"
  115. gpu_status: list[GpuPidUsage] = Field(default_factory=list)
  116. modules: list[ModuleStatus] = Field(default_factory=list)
  117. last_heartbeat: float = 0
  118. alive: bool = True
  119. # --- additive VRAM-capacity + telemetry (per physical GPU) ---
  120. gpus: list[GpuMemInfo] = Field(default_factory=list)
  121. vram_total_mb: int = 0
  122. vram_free_mb: int = 0
  123. driver_version: str = ""
  124. class PlaceRequest(BaseModel):
  125. model: str
  126. priority: PriorityClass = PriorityClass.normal
  127. class PlaceResponse(BaseModel):
  128. node_id: str
  129. node_ip: str
  130. infer_url: str
  131. module_name: str
  132. state: ModuleState
  133. class RegistryModelsResponse(BaseModel):
  134. models: list[ModuleStatus]
  135. nodes_count: int
  136. total_gpu_vram_mb: int = 0
  137. total_gpu_capacity_mb: int = 0
  138. total_gpu_free_mb: int = 0
  139. class RegistryNodesResponse(BaseModel):
  140. nodes: list[NodeInfo]