Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 44bfe634f5 |
+4
-5
@@ -45,7 +45,6 @@ class Metrics:
|
|||||||
self.model_metrics.workload_received += workload
|
self.model_metrics.workload_received += workload
|
||||||
self.model_metrics.requests_recieved.add(reqnum)
|
self.model_metrics.requests_recieved.add(reqnum)
|
||||||
self.model_metrics.requests_working.add(reqnum)
|
self.model_metrics.requests_working.add(reqnum)
|
||||||
self.update_pending = True
|
|
||||||
|
|
||||||
def _request_end(self, workload: float, reqnum: int) -> None:
|
def _request_end(self, workload: float, reqnum: int) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -79,10 +78,10 @@ class Metrics:
|
|||||||
elapsed = time.time() - self.last_metric_update
|
elapsed = time.time() - self.last_metric_update
|
||||||
if self.system_metrics.model_is_loaded is False and elapsed >= 10:
|
if self.system_metrics.model_is_loaded is False and elapsed >= 10:
|
||||||
log.debug(f"sending loading model metrics after {int(elapsed)}s wait")
|
log.debug(f"sending loading model metrics after {int(elapsed)}s wait")
|
||||||
self.__send_metrics_and_reset()
|
self.__send_metrics_and_reset(elapsed)
|
||||||
elif self.update_pending or elapsed > 10:
|
elif self.update_pending or elapsed > 10:
|
||||||
log.debug(f"sending loaded model metrics after {int(elapsed)}s wait")
|
log.debug(f"sending loaded model metrics after {int(elapsed)}s wait")
|
||||||
self.__send_metrics_and_reset()
|
self.__send_metrics_and_reset(elapsed)
|
||||||
|
|
||||||
def _model_loaded(self, max_throughput: float) -> None:
|
def _model_loaded(self, max_throughput: float) -> None:
|
||||||
self.system_metrics.model_loading_time = (
|
self.system_metrics.model_loading_time = (
|
||||||
@@ -97,13 +96,13 @@ class Metrics:
|
|||||||
|
|
||||||
#######################################Private#######################################
|
#######################################Private#######################################
|
||||||
|
|
||||||
def __send_metrics_and_reset(self):
|
def __send_metrics_and_reset(self, elapsed):
|
||||||
|
|
||||||
def compute_autoscaler_data() -> AutoScalaerData:
|
def compute_autoscaler_data() -> AutoScalaerData:
|
||||||
return AutoScalaerData(
|
return AutoScalaerData(
|
||||||
id=self.id,
|
id=self.id,
|
||||||
loadtime=(self.system_metrics.model_loading_time or 0.0),
|
loadtime=(self.system_metrics.model_loading_time or 0.0),
|
||||||
cur_load=(self.model_metrics.workload_processing),
|
cur_load=(self.model_metrics.workload_processing / elapsed),
|
||||||
max_perf=self.model_metrics.max_throughput,
|
max_perf=self.model_metrics.max_throughput,
|
||||||
cur_perf=self.model_metrics.cur_perf,
|
cur_perf=self.model_metrics.cur_perf,
|
||||||
error_msg=self.model_metrics.error_msg or "",
|
error_msg=self.model_metrics.error_msg or "",
|
||||||
|
|||||||
+9
-3
@@ -58,10 +58,16 @@ then
|
|||||||
source ~/.local/bin/env
|
source ~/.local/bin/env
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Fork testing
|
if [[ ! -d $SERVER_DIR ]]; then
|
||||||
[[ ! -d $SERVER_DIR ]] && git clone "${PYWORKER_REPO:-https://github.com/vast-ai/pyworker}" "$SERVER_DIR"
|
git clone --depth=1 "${PYWORKER_REPO:-https://github.com/vast-ai/pyworker}" "$SERVER_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
if [[ -n ${PYWORKER_REF:-} ]]; then
|
if [[ -n ${PYWORKER_REF:-} ]]; then
|
||||||
(cd "$SERVER_DIR" && git checkout "$PYWORKER_REF")
|
(
|
||||||
|
cd "$SERVER_DIR"
|
||||||
|
git fetch --depth=1 origin "$PYWORKER_REF"
|
||||||
|
git checkout "$PYWORKER_REF"
|
||||||
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
uv venv --python-preference only-managed "$ENV_PATH" -p 3.10
|
uv venv --python-preference only-managed "$ENV_PATH" -p 3.10
|
||||||
|
|||||||
@@ -12,21 +12,9 @@ A docker image is provided but you may use any if the above requirements are met
|
|||||||
|
|
||||||
## Benchmarking
|
## Benchmarking
|
||||||
|
|
||||||
### Custom Benchmark Workflows
|
A simple image generation benchmark runs when each worker initializes to validate GPU performance and identify underperforming machines.
|
||||||
|
|
||||||
You can provide a custom ComfyUI workflow for benchmarking by creating `workers/comfyui-json/misc/benchmark.json`. This allows you to test performance using your preferred models and workflow complexity.
|
The benchmark uses Stable Diffusion v1.5 with ComfyUI's default text-to-image workflow. Configure the benchmark complexity and duration using these variables:
|
||||||
|
|
||||||
**Ways to provide the benchmark file:**
|
|
||||||
- Fork this repository and add your `benchmark.json` file
|
|
||||||
- Write the file during worker provisioning (onstart script or setup phase)
|
|
||||||
|
|
||||||
An example file is provided in the repository. To ensure varied generations, use the placeholder `__RANDOM_INT__` in place of static seed values - it will be replaced with a random integer for each benchmark run.
|
|
||||||
|
|
||||||
### Default Benchmark (Fallback)
|
|
||||||
|
|
||||||
If `benchmark.json` is not available, a simple image generation benchmark runs when each worker initializes. This validates GPU performance and helps identify underperforming machines.
|
|
||||||
|
|
||||||
The default benchmark uses Stable Diffusion v1.5 with ComfyUI's standard text-to-image workflow. Configure it using these environment variables:
|
|
||||||
|
|
||||||
| Environment Variable | Default Value | Description |
|
| Environment Variable | Default Value | Description |
|
||||||
| -------------------- | ------------- | ----------- |
|
| -------------------- | ------------- | ----------- |
|
||||||
@@ -36,7 +24,7 @@ The default benchmark uses Stable Diffusion v1.5 with ComfyUI's standard text-to
|
|||||||
|
|
||||||
Each benchmark run uses a random prompt from `misc/test_prompts.txt` and a random seed to ensure consistent GPU load patterns.
|
Each benchmark run uses a random prompt from `misc/test_prompts.txt` and a random seed to ensure consistent GPU load patterns.
|
||||||
|
|
||||||
#### Calibrating Fallback Benchmark Duration
|
### Calibrating Benchmark Duration
|
||||||
|
|
||||||
To screen for underperforming hardware, set `BENCHMARK_TEST_STEPS` to match your expected production workflow duration. This allows you to identify machines that won't meet performance requirements.
|
To screen for underperforming hardware, set `BENCHMARK_TEST_STEPS` to match your expected production workflow duration. This allows you to identify machines that won't meet performance requirements.
|
||||||
|
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ def call_text2image_workflow(
|
|||||||
endpoint=route_response["endpoint"],
|
endpoint=route_response["endpoint"],
|
||||||
reqnum=route_response["reqnum"],
|
reqnum=route_response["reqnum"],
|
||||||
url=route_response["url"],
|
url=route_response["url"],
|
||||||
request_idx=route_response["request_idx"],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build the payload for the worker request
|
# Build the payload for the worker request
|
||||||
|
|||||||
@@ -5,13 +5,12 @@ import dataclasses
|
|||||||
from typing import Dict, Any
|
from typing import Dict, Any
|
||||||
from functools import cache
|
from functools import cache
|
||||||
from math import ceil
|
from math import ceil
|
||||||
from pathlib import Path
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from lib.data_types import ApiPayload, JsonDataException
|
from lib.data_types import ApiPayload, JsonDataException
|
||||||
|
|
||||||
log = logging.getLogger(__file__)
|
|
||||||
|
with open("workers/comfyui/misc/test_prompts.txt", "r") as f:
|
||||||
|
test_prompts = f.readlines()
|
||||||
|
|
||||||
def count_workload() -> float:
|
def count_workload() -> float:
|
||||||
# Always 100.0 where there is a single instance of ComfyUI handling requests
|
# Always 100.0 where there is a single instance of ComfyUI handling requests
|
||||||
@@ -25,32 +24,9 @@ class ComfyWorkflowData(ApiPayload):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def for_test(cls):
|
def for_test(cls):
|
||||||
"""
|
"""
|
||||||
If the user has provided a benchmark workflow we can use it here to properly gauge performance.
|
Use the variables available to simulate workflows of the required running time
|
||||||
Otherwise, use the variables available to simulate workflows of the required running time
|
|
||||||
Example: SD1.5, simple image gen 10000 steps, 512px x 512px will run for approximately 9 minutes @ ~18 it/s (RTX 4090)
|
Example: SD1.5, simple image gen 10000 steps, 512px x 512px will run for approximately 9 minutes @ ~18 it/s (RTX 4090)
|
||||||
"""
|
"""
|
||||||
# Try to load benchmark.json
|
|
||||||
benchmark_file = Path("workers/comfyui-json/misc/benchmark.json")
|
|
||||||
|
|
||||||
if benchmark_file.exists():
|
|
||||||
try:
|
|
||||||
with open(benchmark_file, "r") as f:
|
|
||||||
benchmark_workflow = json.load(f)
|
|
||||||
return cls(
|
|
||||||
input={
|
|
||||||
"request_id": f"test-{random.randint(1000, 99999)}",
|
|
||||||
"workflow_json": benchmark_workflow
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except (json.JSONDecodeError, IOError):
|
|
||||||
# JSON is malformed or file can't be read, fall through to default
|
|
||||||
log.error(f"Failed to benchmark using {benchmark_file}")
|
|
||||||
|
|
||||||
# Fallback: read prompts and construct payload
|
|
||||||
log.info("Using fallback method for benchmarking")
|
|
||||||
with open("workers/comfyui-json/misc/test_prompts.txt", "r") as f:
|
|
||||||
test_prompts = f.readlines()
|
|
||||||
|
|
||||||
test_prompt = random.choice(test_prompts).rstrip()
|
test_prompt = random.choice(test_prompts).rstrip()
|
||||||
return cls(
|
return cls(
|
||||||
input={
|
input={
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
{
|
|
||||||
"3": {
|
|
||||||
"inputs": {
|
|
||||||
"seed": "__RANDOM_INT__",
|
|
||||||
"steps": 20,
|
|
||||||
"cfg": 8,
|
|
||||||
"sampler_name": "euler",
|
|
||||||
"scheduler": "normal",
|
|
||||||
"denoise": 1,
|
|
||||||
"model": [
|
|
||||||
"4",
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"positive": [
|
|
||||||
"6",
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"negative": [
|
|
||||||
"7",
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"latent_image": [
|
|
||||||
"5",
|
|
||||||
0
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"class_type": "KSampler",
|
|
||||||
"_meta": {
|
|
||||||
"title": "KSampler"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"4": {
|
|
||||||
"inputs": {
|
|
||||||
"ckpt_name": "v1-5-pruned-emaonly-fp16.safetensors"
|
|
||||||
},
|
|
||||||
"class_type": "CheckpointLoaderSimple",
|
|
||||||
"_meta": {
|
|
||||||
"title": "Load Checkpoint"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"5": {
|
|
||||||
"inputs": {
|
|
||||||
"width": 512,
|
|
||||||
"height": 512,
|
|
||||||
"batch_size": 1
|
|
||||||
},
|
|
||||||
"class_type": "EmptyLatentImage",
|
|
||||||
"_meta": {
|
|
||||||
"title": "Empty Latent Image"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"6": {
|
|
||||||
"inputs": {
|
|
||||||
"text": "beautiful scenery nature glass bottle landscape, , purple galaxy bottle,",
|
|
||||||
"clip": [
|
|
||||||
"4",
|
|
||||||
1
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"class_type": "CLIPTextEncode",
|
|
||||||
"_meta": {
|
|
||||||
"title": "CLIP Text Encode (Prompt)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"7": {
|
|
||||||
"inputs": {
|
|
||||||
"text": "text, watermark",
|
|
||||||
"clip": [
|
|
||||||
"4",
|
|
||||||
1
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"class_type": "CLIPTextEncode",
|
|
||||||
"_meta": {
|
|
||||||
"title": "CLIP Text Encode (Prompt)"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"8": {
|
|
||||||
"inputs": {
|
|
||||||
"samples": [
|
|
||||||
"3",
|
|
||||||
0
|
|
||||||
],
|
|
||||||
"vae": [
|
|
||||||
"4",
|
|
||||||
2
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"class_type": "VAEDecode",
|
|
||||||
"_meta": {
|
|
||||||
"title": "VAE Decode"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"9": {
|
|
||||||
"inputs": {
|
|
||||||
"filename_prefix": "ComfyUI",
|
|
||||||
"images": [
|
|
||||||
"8",
|
|
||||||
0
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"class_type": "SaveImage",
|
|
||||||
"_meta": {
|
|
||||||
"title": "Save Image"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ MODEL_SERVER_START_LOG_MSG = "To see the GUI go to: "
|
|||||||
MODEL_SERVER_ERROR_LOG_MSGS = [
|
MODEL_SERVER_ERROR_LOG_MSGS = [
|
||||||
"MetadataIncompleteBuffer", # This error is emitted when the downloaded model is corrupted
|
"MetadataIncompleteBuffer", # This error is emitted when the downloaded model is corrupted
|
||||||
"Value not in list: ", # This error is emitted when the model file is not there at all
|
"Value not in list: ", # This error is emitted when the model file is not there at all
|
||||||
"[ERROR] Provisioning Script failed", # Error inserted by provisioning script if models/nodes fail to download
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ def call_custom_workflow_for_sd3(
|
|||||||
endpoint=message["endpoint"],
|
endpoint=message["endpoint"],
|
||||||
reqnum=message["reqnum"],
|
reqnum=message["reqnum"],
|
||||||
url=message["url"],
|
url=message["url"],
|
||||||
request_idx=message["request_idx"],
|
|
||||||
)
|
)
|
||||||
workflow = {
|
workflow = {
|
||||||
"3": {
|
"3": {
|
||||||
|
|||||||
Reference in New Issue
Block a user