From d03cd56da235eb98e64e3f34445c1c045a409c65 Mon Sep 17 00:00:00 2001 From: db0 Date: Thu, 12 Sep 2024 19:22:40 +0200 Subject: [PATCH 01/46] fix: prevent workers without flux support picking up flux jobs --- horde/bridge_reference.py | 1 + horde/classes/stable/worker.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index 7d7a1aea..1c993033 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -9,6 +9,7 @@ BRIDGE_CAPABILITIES = { "AI Horde Worker reGen": { + 9: {"flux"}, 8: {"layer_diffuse"}, 7: {"qr_code", "extra_texts", "workflow"}, 6: {"stable_cascade_2pass"}, diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 182a5d38..5c014330 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -138,6 +138,10 @@ def can_generate(self, waiting_prompt): and not check_bridge_capability("stable_cascade_2pass", self.bridge_agent) ): return [False, "bridge_version"] + if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( + "flux", self.bridge_agent + ): + return [False, "bridge_version"] if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", self.bridge_agent, From c194d6284834fa8ec10d2a7a75c9eaa739d48ceb Mon Sep 17 00:00:00 2001 From: db0 Date: Thu, 12 Sep 2024 19:56:55 +0200 Subject: [PATCH 02/46] feat: adjusted TTL formula to be algorithmic --- horde/classes/stable/waiting_prompt.py | 30 ++++++++++---------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/horde/classes/stable/waiting_prompt.py b/horde/classes/stable/waiting_prompt.py index 0fb6e801..42adf489 100644 --- a/horde/classes/stable/waiting_prompt.py +++ b/horde/classes/stable/waiting_prompt.py @@ -442,28 +442,20 @@ def get_accurate_steps(self): return steps def set_job_ttl(self): - # default is 2 minutes. Then we scale up based on resolution. - # This will be more accurate with a newer formula - self.job_ttl = 120 - if self.width * self.height > 2048 * 2048: - self.job_ttl = 800 - elif self.width * self.height > 1024 * 1024: - self.job_ttl = 400 - elif self.width * self.height > 728 * 728: - self.job_ttl = 260 - elif self.width * self.height >= 512 * 512: - self.job_ttl = 150 - # When too many steps are involved, we increase the expiry time - if self.get_accurate_steps() >= 200: - self.job_ttl = self.job_ttl * 3 - elif self.get_accurate_steps() >= 100: - self.job_ttl = self.job_ttl * 2 + # We are aiming here for a graceful min 2sec/it speed on workers for 512x512 which is well below our requested min 0.5mps/s, + # to buffer for model loading and allow for the occasional slowdown without dropping jobs. + # There is also a minimum of 2mins, regardless of steps and resolution used and an extra 30 seconds for model loading. + # This means a worker at 1mps/s should be able to finish a 512x512x50 request comfortably within 30s but we allow up to 2.5mins. + # This number then increases lineary based on the resolution requested. + # Using this formula, a 1536x768x40 request is expected to take ~50s on a 1mps/s worker, but we will only time out after 390s. + ttl_multiplier = (self.width * self.height) / (512*512) + self.job_ttl = 30 + (self.get_accurate_steps() * 2 * ttl_multiplier) # CN is 3 times slower if self.gen_payload.get("control_type"): self.job_ttl = self.job_ttl * 3 - if "SDXL_beta::stability.ai#6901" in self.get_model_names(): - logger.debug(self.get_model_names()) - self.job_ttl = 300 + # Flux is way slower than Stable Diffusion + if any(model_reference.get_model_baseline(mn) in ["flux_1"] for mn in self.get_model_names()): + self.job_ttl = self.job_ttl * 3 # logger.info([weights_count,self.job_ttl]) db.session.commit() From a49fdf0261972b9b8e4872b88a3bc2aacdc80690 Mon Sep 17 00:00:00 2001 From: db0 Date: Thu, 12 Sep 2024 22:08:40 +0200 Subject: [PATCH 03/46] wip --- CHANGELOG.md | 8 +++++++ horde/apis/models/kobold_v2.py | 4 ++++ horde/apis/models/stable_v2.py | 19 ++++++++++++++++ horde/apis/models/v2.py | 22 +++++++++++++++++++ horde/apis/v2/stable.py | 3 +++ horde/classes/base/processing_generation.py | 13 +++++++++-- horde/classes/base/waiting_prompt.py | 22 +++++++++---------- horde/classes/base/worker.py | 4 +++- horde/classes/kobold/worker.py | 2 +- horde/classes/stable/processing_generation.py | 19 ++++++++++++++++ horde/classes/stable/waiting_prompt.py | 19 ---------------- horde/classes/stable/worker.py | 13 +++++++++++ horde/consts.py | 2 +- horde/database/functions.py | 7 ++++++ horde/database/threads.py | 11 +++++----- horde/utils.py | 4 ++++ sql_statements/4.43.0.txt | 6 +++++ sql_statements/4.43.0.txt.license | 3 +++ 18 files changed, 140 insertions(+), 41 deletions(-) create mode 100644 sql_statements/4.43.0.txt create mode 100644 sql_statements/4.43.0.txt.license diff --git a/CHANGELOG.md b/CHANGELOG.md index b720d3ae..2ef11f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ SPDX-License-Identifier: AGPL-3.0-or-later # Changelog +# 4.43.0 + +* Adjused TTL formula to be algorithmic +* prevent workers without flux support picking up flux jobs +* Adds `extra_slow_workers` bool for image gen async +* Adds `extra_slow_worker` bool for worker pop +* Adds `limit_max_steps` for worker pop + # 4.42.0 * Adds support for the Flux family of models diff --git a/horde/apis/models/kobold_v2.py b/horde/apis/models/kobold_v2.py index 944773a0..79403376 100644 --- a/horde/apis/models/kobold_v2.py +++ b/horde/apis/models/kobold_v2.py @@ -348,6 +348,10 @@ def __init__(self, api): "The request will include the details of the job as well as the request ID." ), ), + "extra_slow_workers": fields.Boolean( + default=False, + description="When True, allows very slower workers to pick up this request. Use this when you don't mind waiting a lot.", + ), }, ) self.response_model_contrib_details = api.inherit( diff --git a/horde/apis/models/stable_v2.py b/horde/apis/models/stable_v2.py index 50620ed7..d1ccb02c 100644 --- a/horde/apis/models/stable_v2.py +++ b/horde/apis/models/stable_v2.py @@ -152,6 +152,14 @@ def __init__(self): help="If True, this worker will pick up requests requesting LoRas.", location="json", ) + self.job_pop_parser.add_argument( + "limit_max_steps", + type=bool, + required=False, + default=False, + help="If True, This worker will not pick up jobs with more steps than the average allowed for that model.", + location="json", + ) self.job_submit_parser.add_argument( "seed", type=int, @@ -544,6 +552,13 @@ def __init__(self, api): default=True, description="If True, this worker will pick up requests requesting LoRas.", ), + "limit_max_steps": fields.Boolean( + default=True, + description=( + "If True, This worker will not pick up jobs with more steps than the average allowed for that model." + " this is for use by workers which might run into issues doing too many steps." + ), + ), }, ) self.input_model_job_submit = api.inherit( @@ -591,6 +606,10 @@ def __init__(self, api): default=True, description="When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", ), + "extra_slow_workers": fields.Boolean( + default=False, + description="When True, allows very slower workers to pick up this request. Use this when you don't mind waiting a lot.", + ), "censor_nsfw": fields.Boolean( default=False, description="If the request is SFW, and the worker accidentally generates NSFW, it will send back a censored image.", diff --git a/horde/apis/models/v2.py b/horde/apis/models/v2.py index 6e1e1079..863c03bb 100644 --- a/horde/apis/models/v2.py +++ b/horde/apis/models/v2.py @@ -101,6 +101,14 @@ def __init__(self): help="When True, allows slower workers to pick up this request. Disabling this incurs an extra kudos cost.", location="json", ) + self.generate_parser.add_argument( + "extra_slow_workers", + type=bool, + default=False, + required=False, + help="When True, allows very slower workers to pick up this request. Use this when you don't mind waiting a lot.", + location="json", + ) self.generate_parser.add_argument( "dry_run", type=bool, @@ -204,6 +212,13 @@ def __init__(self): help="How many jobvs to pop at the same time", location="json", ) + self.job_pop_parser.add_argument( + "extra_slow_worker", + type=bool, + default=False, + required=False, + location="json", + ) self.job_submit_parser = reqparse.RequestParser() self.job_submit_parser.add_argument( @@ -537,6 +552,13 @@ def __init__(self, api): min=1, max=20, ), + "extra_slow_worker": fields.Boolean( + default=True, + description=( + "If True, marks the worker as very slow. You should only use this if your mps/s is lower than 0.1." + "Extra slow workers are excluded from normal requests but users can opt in to use them." + ), + ), }, ) self.response_model_worker_details = api.inherit( diff --git a/horde/apis/v2/stable.py b/horde/apis/v2/stable.py index 26f6f4f5..122887fc 100644 --- a/horde/apis/v2/stable.py +++ b/horde/apis/v2/stable.py @@ -300,6 +300,7 @@ def initiate_waiting_prompt(self): validated_backends=self.args.validated_backends, worker_blacklist=self.args.worker_blacklist, slow_workers=self.args.slow_workers, + extra_slow_workers=self.args.extra_slow_workers, source_processing=self.args.source_processing, ipaddr=self.user_ip, safe_ip=self.safe_ip, @@ -621,6 +622,8 @@ def check_in(self): allow_controlnet=self.args.allow_controlnet, allow_sdxl_controlnet=self.args.allow_sdxl_controlnet, allow_lora=self.args.allow_lora, + extra_slow_worker=self.args.extra_slow_worker, + limit_max_steps=self.args.limit_max_steps, priority_usernames=self.priority_usernames, ) diff --git a/horde/classes/base/processing_generation.py b/horde/classes/base/processing_generation.py index febdd009..9a95f836 100644 --- a/horde/classes/base/processing_generation.py +++ b/horde/classes/base/processing_generation.py @@ -44,6 +44,7 @@ class ProcessingGeneration(db.Model): nullable=False, server_default=expression.literal(False), ) + job_ttl = db.Column(db.Integer, default=150, nullable=False, index=True) wp_id = db.Column( uuid_column_type(), @@ -80,6 +81,7 @@ def __init__(self, *args, **kwargs): self.model = matching_models[0] else: self.model = kwargs["model"] + self.set_job_ttl() db.session.commit() def set_generation(self, generation, things_per_sec, **kwargs): @@ -163,10 +165,10 @@ def is_completed(self): def is_faulted(self): return self.faulted - def is_stale(self, ttl): + def is_stale(self): if self.is_completed() or self.is_faulted(): return False - return (datetime.utcnow() - self.start_time).total_seconds() > ttl + return (datetime.utcnow() - self.start_time).total_seconds() > self.job_ttl def delete(self): db.session.delete(self) @@ -224,3 +226,10 @@ def send_webhook(self, kudos): break except Exception as err: logger.debug(f"Exception when sending generation webhook: {err}. Will retry {3-riter-1} more times...") + + def set_job_ttl(self): + """Returns how many seconds each job request should stay waiting before considering it stale and cancelling it + This function should be overriden by the invididual hordes depending on how the calculating ttl + """ + self.job_ttl = 150 + db.session.commit() diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index 0a8c26cf..c0b93961 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -19,7 +19,7 @@ from horde.classes.stable.processing_generation import ImageProcessingGeneration from horde.flask import SQLITE_MODE, db from horde.logger import logger -from horde.utils import get_db_uuid, get_expiry_date +from horde.utils import get_db_uuid, get_expiry_date, get_extra_slow_expiry_date procgen_classes = { "template": ProcessingGeneration, @@ -105,6 +105,7 @@ class WaitingPrompt(db.Model): things = db.Column(db.BigInteger, default=0, nullable=False) total_usage = db.Column(db.Float, default=0, nullable=False) extra_priority = db.Column(db.Integer, default=0, nullable=False, index=True) + # TODO: Delete. Obsoleted. job_ttl = db.Column(db.Integer, default=150, nullable=False) disable_batching = db.Column(db.Boolean, default=False, nullable=False) webhook = db.Column(db.String(1024)) @@ -204,7 +205,6 @@ def extract_params(self): self.things = 0 self.total_usage = round(self.things * self.n, 2) self.prepare_job_payload() - self.set_job_ttl() db.session.commit() def prepare_job_payload(self): @@ -241,7 +241,7 @@ def start_generation(self, worker, amount=1): self.n -= safe_amount payload = self.get_job_payload(current_n) # This does a commit as well - self.refresh() + self.refresh(worker) procgen_class = procgen_classes[self.wp_type] gens_list = [] model = None @@ -457,8 +457,13 @@ def abort_for_maintenance(self): except Exception as err: logger.warning(f"Error when aborting WP. Skipping: {err}") - def refresh(self): - self.expiry = get_expiry_date() + def refresh(self, worker=None): + if self.n > 0 and worker is not None and worker.extra_slow_worker is True: + self.expiry = get_extra_slow_expiry_date() + else: + new_expiry = get_expiry_date() + if self.expiry < new_expiry: + self.expiry = new_expiry db.session.commit() def is_stale(self): @@ -469,13 +474,6 @@ def is_stale(self): def get_priority(self): return self.extra_priority - def set_job_ttl(self): - """Returns how many seconds each job request should stay waiting before considering it stale and cancelling it - This function should be overriden by the invididual hordes depending on how the calculating ttl - """ - self.job_ttl = 150 - db.session.commit() - def refresh_worker_cache(self): worker_ids = [worker.worker_id for worker in self.workers] worker_string_ids = [str(worker.worker_id) for worker in self.workers] diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index aecd3669..147f9088 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -121,6 +121,7 @@ class WorkerTemplate(db.Model): # Used by all workers to record how much they can pick up to generate # The value of this column is dfferent per worker type max_power = db.Column(db.Integer, default=20, nullable=False) + extra_slow_worker = db.Column(db.Boolean, default=False, nullable=False) paused = db.Column(db.Boolean, default=False, nullable=False) maintenance = db.Column(db.Boolean, default=False, nullable=False) @@ -511,7 +512,8 @@ def check_in(self, **kwargs): self.set_models(kwargs.get("models")) self.nsfw = kwargs.get("nsfw", True) self.set_blacklist(kwargs.get("blacklist", [])) - db.session.commit() + self.extra_slow_worker = kwargs.get("extra_slow_worker", False) + # Commit should happen on calling extensions def set_blacklist(self, blacklist): # We don't allow more workers to claim they can server more than 50 models atm (to prevent abuse) diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index f9ce7a49..4bd0cafe 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -49,7 +49,7 @@ def check_in(self, max_length, max_context_length, softprompts, **kwargs): super().check_in(**kwargs) self.max_length = max_length self.max_context_length = max_context_length - self.set_softprompts(softprompts) + self.set_softprompts(softprompts) # Does a commit as well paused_string = "" if self.paused: paused_string = "(Paused) " diff --git a/horde/classes/stable/processing_generation.py b/horde/classes/stable/processing_generation.py index d4a984ba..87808d80 100644 --- a/horde/classes/stable/processing_generation.py +++ b/horde/classes/stable/processing_generation.py @@ -143,3 +143,22 @@ def upload_generation_metadata(self): f.write(json_object) upload_shared_metadata(filename) os.remove(filename) + + def set_job_ttl(self): + # We are aiming here for a graceful min 2sec/it speed on workers for 512x512 which is well below our requested min 0.5mps/s, + # to buffer for model loading and allow for the occasional slowdown without dropping jobs. + # There is also a minimum of 2mins, regardless of steps and resolution used and an extra 30 seconds for model loading. + # This means a worker at 1mps/s should be able to finish a 512x512x50 request comfortably within 30s but we allow up to 2.5mins. + # This number then increases lineary based on the resolution requested. + # Using this formula, a 1536x768x40 request is expected to take ~50s on a 1mps/s worker, but we will only time out after 390s. + ttl_multiplier = (self.wp.width * self.wp.height) / (512 * 512) + self.job_ttl = 30 + (self.wp.get_accurate_steps() * 2 * ttl_multiplier) + # CN is 3 times slower + if self.wp.gen_payload.get("control_type"): + self.job_ttl = self.job_ttl * 2 + # Flux is way slower than Stable Diffusion + if any(model_reference.get_model_baseline(mn) in ["flux_1"] for mn in self.wp.get_model_names()): + self.job_ttl = self.job_ttl * 3 + if self.worker.extra_slow_worker is True: + self.job_ttl = self.job_ttl * 3 + db.session.commit() diff --git a/horde/classes/stable/waiting_prompt.py b/horde/classes/stable/waiting_prompt.py index 42adf489..5c29771e 100644 --- a/horde/classes/stable/waiting_prompt.py +++ b/horde/classes/stable/waiting_prompt.py @@ -125,7 +125,6 @@ def extract_params(self): self.trusted_workers = True self.shared = False self.prepare_job_payload(self.params) - self.set_job_ttl() # Commit will happen in prepare_job_payload() @logger.catch(reraise=True) @@ -441,24 +440,6 @@ def get_accurate_steps(self): steps *= 2 return steps - def set_job_ttl(self): - # We are aiming here for a graceful min 2sec/it speed on workers for 512x512 which is well below our requested min 0.5mps/s, - # to buffer for model loading and allow for the occasional slowdown without dropping jobs. - # There is also a minimum of 2mins, regardless of steps and resolution used and an extra 30 seconds for model loading. - # This means a worker at 1mps/s should be able to finish a 512x512x50 request comfortably within 30s but we allow up to 2.5mins. - # This number then increases lineary based on the resolution requested. - # Using this formula, a 1536x768x40 request is expected to take ~50s on a 1mps/s worker, but we will only time out after 390s. - ttl_multiplier = (self.width * self.height) / (512*512) - self.job_ttl = 30 + (self.get_accurate_steps() * 2 * ttl_multiplier) - # CN is 3 times slower - if self.gen_payload.get("control_type"): - self.job_ttl = self.job_ttl * 3 - # Flux is way slower than Stable Diffusion - if any(model_reference.get_model_baseline(mn) in ["flux_1"] for mn in self.get_model_names()): - self.job_ttl = self.job_ttl * 3 - # logger.info([weights_count,self.job_ttl]) - db.session.commit() - def log_faulted_prompt(self): source_processing = "txt2img" if self.source_image: diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 5c014330..ced57d48 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -29,6 +29,7 @@ class ImageWorker(Worker): allow_controlnet = db.Column(db.Boolean, default=False, nullable=False) allow_sdxl_controlnet = db.Column(db.Boolean, default=False, nullable=False) allow_lora = db.Column(db.Boolean, default=False, nullable=False) + limit_max_steps = db.Column(db.Boolean, default=False, nullable=False) wtype = "image" def check_in(self, max_pixels, **kwargs): @@ -43,6 +44,7 @@ def check_in(self, max_pixels, **kwargs): self.allow_controlnet = kwargs.get("allow_controlnet", False) self.allow_sdxl_controlnet = kwargs.get("allow_sdxl_controlnet", False) self.allow_lora = kwargs.get("allow_lora", False) + self.limit_max_steps = kwargs.get("limit_max_steps", False) if len(self.get_model_names()) == 0: self.set_models(["stable_diffusion"]) paused_string = "" @@ -154,6 +156,17 @@ def can_generate(self, waiting_prompt): return [False, "bridge_version"] if not waiting_prompt.safe_ip and not self.allow_unsafe_ipaddr: return [False, "unsafe_ip"] + if self.limit_max_steps: + for mn in waiting_prompt.model_names(): + avg_steps = ( + int( + model_reference.get_model_requirements(mn).get("min_steps", 20) + + model_reference.get_model_requirements(mn).get("max_steps", 40) + ) + / 2 + ) + if waiting_prompt.get_accurate_steps() > avg_steps: + return [False, "unsafe_ip"] # We do not give untrusted workers anon or VPN generations, to avoid anything slipping by and spooking them. # logger.warning(datetime.utcnow()) if not self.user.trusted: # FIXME #noqa SIM102 diff --git a/horde/consts.py b/horde/consts.py index c20879d4..703e34bc 100644 --- a/horde/consts.py +++ b/horde/consts.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -HORDE_VERSION = "4.42.0 " +HORDE_VERSION = "4.43.0 " WHITELISTED_SERVICE_IPS = { "212.227.227.178", # Turing Bot diff --git a/horde/database/functions.py b/horde/database/functions.py index affc1e50..30997443 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -840,6 +840,13 @@ def get_sorted_wp_filtered_to_worker(worker, models_list=None, blacklist=None, p worker.speed >= 500000, # 0.5 MPS/s ImageWaitingPrompt.slow_workers == True, # noqa E712 ), + or_( + worker.extra_slow_worker == False, + and_( + worker.extra_slow_worker == True, + ImageWaitingPrompt.extra_slow_workers == True, # noqa E712 + ), + ), or_( not_(ImageWaitingPrompt.params.has_key("transparent")), ImageWaitingPrompt.params["transparent"].astext.cast(Boolean).is_(False), diff --git a/horde/database/threads.py b/horde/database/threads.py index 5c78c1b9..38847c13 100644 --- a/horde/database/threads.py +++ b/horde/database/threads.py @@ -204,17 +204,18 @@ def check_waiting_prompts(): .filter( procgen_class.generation == None, # noqa E712 procgen_class.faulted == False, # noqa E712 - # cutoff_time - procgen_class.start_time > wp_class.job_ttl, - # How do we calculate this in the query? Maybe I need to - # set an expiry time iun procgen as well better? + # TODO: How do we calculate this in the query? + # cutoff_time - procgen_class.start_time > procgen_class.job_ttl, ) .all() ) + modifed_procgens = 0 for proc_gen in all_proc_gen: - if proc_gen.is_stale(proc_gen.wp.job_ttl): + if proc_gen.is_stale(): proc_gen.abort() proc_gen.wp.n += 1 - if len(all_proc_gen) >= 1: + modifed_procgens += 1 + if modifed_procgens >= 1: db.session.commit() # Faults WP with 3 or more faulted Procgens wp_ids = ( diff --git a/horde/utils.py b/horde/utils.py index e4389b75..ea20cd2a 100644 --- a/horde/utils.py +++ b/horde/utils.py @@ -101,6 +101,10 @@ def get_expiry_date(): return datetime.utcnow() + dateutil.relativedelta.relativedelta(minutes=+20) +def get_extra_slow_expiry_date(): + return datetime.utcnow() + dateutil.relativedelta.relativedelta(minutes=+60) + + def get_interrogation_form_expiry_date(): return datetime.utcnow() + dateutil.relativedelta.relativedelta(minutes=+3) diff --git a/sql_statements/4.43.0.txt b/sql_statements/4.43.0.txt new file mode 100644 index 00000000..be7a41b2 --- /dev/null +++ b/sql_statements/4.43.0.txt @@ -0,0 +1,6 @@ +ALTER TABLE waiting_prompts ADD COLUMN extra_slow_workers BOOLEAN default false; +ALTER TABLE workers ADD COLUMN extra_slow_worker BOOLEAN default false; +ALTER TABLE workers ADD COLUMN limit_max_steps BOOLEAN default false; +ALTER TABLE processing_gens ADD COLUMN job_ttl BOOLEAN default false; +ALTER TABLE processing_gens ADD COLUMN job_ttl INTEGER NOT NULL DEFAULT 150; +CREATE INDEX idx_processing_gens_job_ttl ON public.processing_gens USING btree(job_ttl); diff --git a/sql_statements/4.43.0.txt.license b/sql_statements/4.43.0.txt.license new file mode 100644 index 00000000..8140c6e2 --- /dev/null +++ b/sql_statements/4.43.0.txt.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: Konstantinos Thoukydidis + +SPDX-License-Identifier: AGPL-3.0-or-later From acb31bed249a32469c81a51ac22686a66a4159d1 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 09:09:15 +0200 Subject: [PATCH 04/46] reporting missing requests due to step count --- horde/apis/models/stable_v2.py | 3 +++ horde/classes/stable/worker.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/horde/apis/models/stable_v2.py b/horde/apis/models/stable_v2.py index d1ccb02c..e3e7959b 100644 --- a/horde/apis/models/stable_v2.py +++ b/horde/apis/models/stable_v2.py @@ -459,6 +459,9 @@ def __init__(self, api): "max_pixels": fields.Integer( description="How many waiting requests were skipped because they demanded a higher size than this worker provides.", ), + "step_count": fields.Integer( + description="How many waiting requests were skipped because they demanded a higher step count that the worker wants.", + ), "unsafe_ip": fields.Integer( description="How many waiting requests were skipped because they came from an unsafe IP.", ), diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index ced57d48..68ad1904 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -166,7 +166,7 @@ def can_generate(self, waiting_prompt): / 2 ) if waiting_prompt.get_accurate_steps() > avg_steps: - return [False, "unsafe_ip"] + return [False, "step_count"] # We do not give untrusted workers anon or VPN generations, to avoid anything slipping by and spooking them. # logger.warning(datetime.utcnow()) if not self.user.trusted: # FIXME #noqa SIM102 From b0c6615c2094c51d63e2a4981f28e27b32cb49ff Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 09:19:43 +0200 Subject: [PATCH 05/46] fix: missing orm declaration: --- horde/classes/base/waiting_prompt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index c0b93961..61247472 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -93,6 +93,7 @@ class WaitingPrompt(db.Model): trusted_workers = db.Column(db.Boolean, default=False, nullable=False, index=True) validated_backends = db.Column(db.Boolean, default=True, nullable=False, index=True) slow_workers = db.Column(db.Boolean, default=True, nullable=False, index=True) + extra_slow_workers = db.Column(db.Boolean, default=True, nullable=False, index=True) worker_blacklist = db.Column(db.Boolean, default=False, nullable=False, index=True) faulted = db.Column(db.Boolean, default=False, nullable=False, index=True) active = db.Column(db.Boolean, default=False, nullable=False, index=True) From 5b81fb06937bf42f0665dca97fe63b33bd17c873 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 09:20:15 +0200 Subject: [PATCH 06/46] fix: missing orm declaration --- horde/classes/base/waiting_prompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index 61247472..4fe84379 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -93,7 +93,7 @@ class WaitingPrompt(db.Model): trusted_workers = db.Column(db.Boolean, default=False, nullable=False, index=True) validated_backends = db.Column(db.Boolean, default=True, nullable=False, index=True) slow_workers = db.Column(db.Boolean, default=True, nullable=False, index=True) - extra_slow_workers = db.Column(db.Boolean, default=True, nullable=False, index=True) + extra_slow_workers = db.Column(db.Boolean, default=False, nullable=False, index=True) worker_blacklist = db.Column(db.Boolean, default=False, nullable=False, index=True) faulted = db.Column(db.Boolean, default=False, nullable=False, index=True) active = db.Column(db.Boolean, default=False, nullable=False, index=True) From a63bad871e9f86b8a59d62167ab133cc0d1496ef Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 09:42:31 +0200 Subject: [PATCH 07/46] feat: support betas --- horde/classes/base/worker.py | 2 +- horde/model_reference.py | 9 +++- sql_statements/4.43.0.txt | 2 + tests/test_image.py | 86 ++++++++++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index 147f9088..b2d74ade 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -121,7 +121,7 @@ class WorkerTemplate(db.Model): # Used by all workers to record how much they can pick up to generate # The value of this column is dfferent per worker type max_power = db.Column(db.Integer, default=20, nullable=False) - extra_slow_worker = db.Column(db.Boolean, default=False, nullable=False) + extra_slow_worker = db.Column(db.Boolean, default=False, nullable=False, index=True) paused = db.Column(db.Boolean, default=False, nullable=False) maintenance = db.Column(db.Boolean, default=False, nullable=False) diff --git a/horde/model_reference.py b/horde/model_reference.py index 9b53b20e..0e45aa84 100644 --- a/horde/model_reference.py +++ b/horde/model_reference.py @@ -5,6 +5,7 @@ import os import requests +from datetime import datetime from horde.logger import logger from horde.threads import PrimaryTimedFunction @@ -24,15 +25,19 @@ class ModelReference(PrimaryTimedFunction): testing_models = {} def call_function(self): - """Retrieves to nataili and text model reference and stores in it a var""" + """Retrieves to image and text model reference and stores in it a var""" # If it's running in SQLITE_MODE, it means it's a test and we never want to grab the quorum # We don't want to report on any random model name a client might request for _riter in range(10): try: + ref_json = "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/main/stable_diffusion.json" + if datetime.utcnow() <= datetime(2024, 9, 30): # Flux Beta + ref_json = "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/refs/heads/flux/stable_diffusion.json" + logger.debug("Using flux beta model reference...") self.reference = requests.get( os.getenv( "HORDE_IMAGE_COMPVIS_REFERENCE", - "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/main/stable_diffusion.json", + ref_json, ), timeout=2, ).json() diff --git a/sql_statements/4.43.0.txt b/sql_statements/4.43.0.txt index be7a41b2..b472e993 100644 --- a/sql_statements/4.43.0.txt +++ b/sql_statements/4.43.0.txt @@ -4,3 +4,5 @@ ALTER TABLE workers ADD COLUMN limit_max_steps BOOLEAN default false; ALTER TABLE processing_gens ADD COLUMN job_ttl BOOLEAN default false; ALTER TABLE processing_gens ADD COLUMN job_ttl INTEGER NOT NULL DEFAULT 150; CREATE INDEX idx_processing_gens_job_ttl ON public.processing_gens USING btree(job_ttl); +CREATE INDEX idx_workers_extra_slow_worker ON public.workers USING btree(extra_slow_worker); +CREATE INDEX idx_waiting_prompts_extra_slow_workers ON public.waiting_prompts USING btree(extra_slow_workers); diff --git a/tests/test_image.py b/tests/test_image.py index 109d0ca7..5c4685b6 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -95,6 +95,92 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True +def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: + headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user + async_dict = { + "prompt": "a horde of cute stable robots in a sprawling server room repairing a massive mainframe", + "nsfw": True, + "censor_nsfw": False, + "r2": True, + "shared": True, + "trusted_workers": True, + "params": { + "width": 1024, + "height": 1024, + "steps": 8, + "cfg_scale": 1.5, + "sampler_name": "k_euler_a", + }, + "sampler_name": "k_euler_a", + "models": TEST_MODELS, + "loras": [{"name": "247778", "is_version": True}], + } + protocol = "http" + if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: + protocol = "https" + async_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/async", json=async_dict, headers=headers) + assert async_req.ok, async_req.text + async_results = async_req.json() + req_id = async_results["id"] + # print(async_results) + print(async_results) + pop_dict = { + "name": "CICD Fake Dreamer", + "models": TEST_MODELS, + "bridge_agent": "AI Horde Worker reGen:8.0.1-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "nsfw": True, + "amount": 10, + "max_pixels": 4194304, + "allow_img2img": True, + "allow_painting": True, + "allow_unsafe_ipaddr": True, + "allow_post_processing": True, + "allow_controlnet": True, + "allow_sdxl_controlnet": True, + "allow_lora": True, + } + pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) + try: + print(pop_req.text) + assert pop_req.ok, pop_req.text + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + pop_results = pop_req.json() + print(json.dumps(pop_results, indent=4)) + + job_id = pop_results["id"] + try: + assert job_id is not None, pop_results + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + submit_dict = { + "id": job_id, + "generation": "R2", + "state": "ok", + "seed": 0, + } + submit_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/submit", json=submit_dict, headers=headers) + assert submit_req.ok, submit_req.text + submit_results = submit_req.json() + assert submit_results["reward"] > 0 + retrieve_req = requests.get(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + assert retrieve_req.ok, retrieve_req.text + retrieve_results = retrieve_req.json() + print(json.dumps(retrieve_results, indent=4)) + assert len(retrieve_results["generations"]) == 1 + gen = retrieve_results["generations"][0] + assert len(gen["gen_metadata"]) == 0 + assert gen["seed"] == "0" + assert gen["worker_name"] == "CICD Fake Dreamer" + assert gen["model"] in TEST_MODELS + assert gen["state"] == "ok" + assert retrieve_results["kudos"] > 1 + assert retrieve_results["done"] is True + if __name__ == "__main__": # "ci/cd#12285" From 9c591e9e6303e5c6949281da4c8f78a2f9603880 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 09:56:50 +0200 Subject: [PATCH 08/46] test --- horde/database/functions.py | 7 ------- tests/test_image.py | 21 +++++++++++---------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 30997443..affc1e50 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -840,13 +840,6 @@ def get_sorted_wp_filtered_to_worker(worker, models_list=None, blacklist=None, p worker.speed >= 500000, # 0.5 MPS/s ImageWaitingPrompt.slow_workers == True, # noqa E712 ), - or_( - worker.extra_slow_worker == False, - and_( - worker.extra_slow_worker == True, - ImageWaitingPrompt.extra_slow_workers == True, # noqa E712 - ), - ), or_( not_(ImageWaitingPrompt.params.has_key("transparent")), ImageWaitingPrompt.params["transparent"].astext.cast(Boolean).is_(False), diff --git a/tests/test_image.py b/tests/test_image.py index 5c4685b6..a3cb9e94 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -95,7 +95,7 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True -def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: +def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user async_dict = { "prompt": "a horde of cute stable robots in a sprawling server room repairing a massive mainframe", @@ -108,12 +108,11 @@ def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None "width": 1024, "height": 1024, "steps": 8, - "cfg_scale": 1.5, - "sampler_name": "k_euler_a", + "cfg_scale": 1, + "sampler_name": "k_euler", }, - "sampler_name": "k_euler_a", - "models": TEST_MODELS, - "loras": [{"name": "247778", "is_version": True}], + "models": ["Flux.1-Schnell fp8 (Compact)"], + # "extra_slow_workers": True, } protocol = "http" if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: @@ -122,12 +121,11 @@ def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None assert async_req.ok, async_req.text async_results = async_req.json() req_id = async_results["id"] - # print(async_results) print(async_results) pop_dict = { "name": "CICD Fake Dreamer", - "models": TEST_MODELS, - "bridge_agent": "AI Horde Worker reGen:8.0.1-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "models": ["Flux.1-Schnell fp8 (Compact)"], + "bridge_agent": "AI Horde Worker reGen:9.0.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, @@ -138,6 +136,8 @@ def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None "allow_controlnet": True, "allow_sdxl_controlnet": True, "allow_lora": True, + # "extra_slow_worker": True, + # "limit_max_steps": True, } pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: @@ -184,4 +184,5 @@ def test_limited_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None if __name__ == "__main__": # "ci/cd#12285" - test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") From b6dc735efd2a739fa44e481b19770e37228fa62b Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:00:03 +0200 Subject: [PATCH 09/46] test --- horde/database/functions.py | 7 +++++++ tests/test_image.py | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index affc1e50..30997443 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -840,6 +840,13 @@ def get_sorted_wp_filtered_to_worker(worker, models_list=None, blacklist=None, p worker.speed >= 500000, # 0.5 MPS/s ImageWaitingPrompt.slow_workers == True, # noqa E712 ), + or_( + worker.extra_slow_worker == False, + and_( + worker.extra_slow_worker == True, + ImageWaitingPrompt.extra_slow_workers == True, # noqa E712 + ), + ), or_( not_(ImageWaitingPrompt.params.has_key("transparent")), ImageWaitingPrompt.params["transparent"].astext.cast(Boolean).is_(False), diff --git a/tests/test_image.py b/tests/test_image.py index a3cb9e94..12291923 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -7,6 +7,7 @@ import requests TEST_MODELS = ["Fustercluck", "AlbedoBase XL (SDXL)"] +TEST_MODELS = ["Flux.1-Schnell fp8 (Compact)"] def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: @@ -184,5 +185,5 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: if __name__ == "__main__": # "ci/cd#12285" - # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") - test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + # test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") From c2f6bc49565c0ee8db060730a2b6b64496c212c6 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:01:40 +0200 Subject: [PATCH 10/46] test --- horde/classes/stable/worker.py | 1 + tests/test_image.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 68ad1904..78f85f4d 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -140,6 +140,7 @@ def can_generate(self, waiting_prompt): and not check_bridge_capability("stable_cascade_2pass", self.bridge_agent) ): return [False, "bridge_version"] + logger.debug('test') if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", self.bridge_agent ): diff --git a/tests/test_image.py b/tests/test_image.py index 12291923..a3cb9e94 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -7,7 +7,6 @@ import requests TEST_MODELS = ["Fustercluck", "AlbedoBase XL (SDXL)"] -TEST_MODELS = ["Flux.1-Schnell fp8 (Compact)"] def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: @@ -185,5 +184,5 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: if __name__ == "__main__": # "ci/cd#12285" - test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") - # test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") From ba20af26bac6af55f7dd6c793305e40bc185c94d Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:04:08 +0200 Subject: [PATCH 11/46] test --- horde/bridge_reference.py | 1 + horde/classes/stable/worker.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index 1c993033..9dadbb6a 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -186,6 +186,7 @@ def parse_bridge_agent(bridge_agent): @logger.catch(reraise=True) def check_bridge_capability(capability, bridge_agent): bridge_name, bridge_version = parse_bridge_agent(bridge_agent) + logger.debug([bridge_name, bridge_version]) if bridge_name not in BRIDGE_CAPABILITIES: return False total_capabilities = set() diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 78f85f4d..1cf11036 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -140,11 +140,12 @@ def can_generate(self, waiting_prompt): and not check_bridge_capability("stable_cascade_2pass", self.bridge_agent) ): return [False, "bridge_version"] - logger.debug('test') if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", self.bridge_agent ): + logger.debug('flux') return [False, "bridge_version"] + logger.debug('test') if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", self.bridge_agent, From c6db21f6f19b7fdffcf498556463d407b38a5feb Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:05:30 +0200 Subject: [PATCH 12/46] test --- horde/bridge_reference.py | 1 + 1 file changed, 1 insertion(+) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index 9dadbb6a..6f598d05 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -196,6 +196,7 @@ def check_bridge_capability(capability, bridge_agent): if checked_semver.compare(bridge_version) <= 0: total_capabilities.update(BRIDGE_CAPABILITIES[bridge_name][version]) # logger.debug([total_capabilities, capability, capability in total_capabilities]) + logger.debug(total_capabilities) return capability in total_capabilities From 76706e785601f8427c36f6d90b204188402c403d Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:05:42 +0200 Subject: [PATCH 13/46] test --- horde/bridge_reference.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index 6f598d05..f966de84 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -195,8 +195,7 @@ def check_bridge_capability(capability, bridge_agent): checked_semver = semver.Version.parse(str(version), True) if checked_semver.compare(bridge_version) <= 0: total_capabilities.update(BRIDGE_CAPABILITIES[bridge_name][version]) - # logger.debug([total_capabilities, capability, capability in total_capabilities]) - logger.debug(total_capabilities) + logger.debug([total_capabilities, capability, capability in total_capabilities]) return capability in total_capabilities From 7805b6c85897ca58cc3ec1101a3b1f87c951cef9 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:09:28 +0200 Subject: [PATCH 14/46] test --- horde/bridge_reference.py | 1 + tests/test_image.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index f966de84..b8d31704 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -196,6 +196,7 @@ def check_bridge_capability(capability, bridge_agent): if checked_semver.compare(bridge_version) <= 0: total_capabilities.update(BRIDGE_CAPABILITIES[bridge_name][version]) logger.debug([total_capabilities, capability, capability in total_capabilities]) + logger.debug([bridge_name, BRIDGE_CAPABILITIES[bridge_name]]) return capability in total_capabilities diff --git a/tests/test_image.py b/tests/test_image.py index a3cb9e94..d259fcd9 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -125,7 +125,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: pop_dict = { "name": "CICD Fake Dreamer", "models": ["Flux.1-Schnell fp8 (Compact)"], - "bridge_agent": "AI Horde Worker reGen:9.0.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "bridge_agent": "AI Horde Worker reGen:9.0.0:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, From 04978910ac609a37d56f2fa93fa6645b177d7021 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:11:30 +0200 Subject: [PATCH 15/46] test --- horde/bridge_reference.py | 4 ++-- horde/classes/stable/worker.py | 2 -- tests/test_image.py | 12 ++++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index b8d31704..7c9ff85c 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -195,8 +195,8 @@ def check_bridge_capability(capability, bridge_agent): checked_semver = semver.Version.parse(str(version), True) if checked_semver.compare(bridge_version) <= 0: total_capabilities.update(BRIDGE_CAPABILITIES[bridge_name][version]) - logger.debug([total_capabilities, capability, capability in total_capabilities]) - logger.debug([bridge_name, BRIDGE_CAPABILITIES[bridge_name]]) + # logger.debug([total_capabilities, capability, capability in total_capabilities]) + # logger.debug([bridge_name, BRIDGE_CAPABILITIES[bridge_name]]) return capability in total_capabilities diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 1cf11036..68ad1904 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -143,9 +143,7 @@ def can_generate(self, waiting_prompt): if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", self.bridge_agent ): - logger.debug('flux') return [False, "bridge_version"] - logger.debug('test') if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", self.bridge_agent, diff --git a/tests/test_image.py b/tests/test_image.py index d259fcd9..b1a17407 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -95,6 +95,10 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True +TEST_MODELS_FLUX = ["Flux.1-Schnell fp8 (Compact)"] + + + def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user async_dict = { @@ -111,7 +115,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "cfg_scale": 1, "sampler_name": "k_euler", }, - "models": ["Flux.1-Schnell fp8 (Compact)"], + "models": TEST_MODELS_FLUX, # "extra_slow_workers": True, } protocol = "http" @@ -124,8 +128,8 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: print(async_results) pop_dict = { "name": "CICD Fake Dreamer", - "models": ["Flux.1-Schnell fp8 (Compact)"], - "bridge_agent": "AI Horde Worker reGen:9.0.0:https://github.com/Haidra-Org/horde-worker-reGen", + "models": TEST_MODELS_FLUX, + "bridge_agent": "AI Horde Worker reGen:9.0.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, @@ -176,7 +180,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert len(gen["gen_metadata"]) == 0 assert gen["seed"] == "0" assert gen["worker_name"] == "CICD Fake Dreamer" - assert gen["model"] in TEST_MODELS + assert gen["model"] in TEST_MODELS_FLUX assert gen["state"] == "ok" assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True From f6bf3973f801cb407115b597d22e5778f750d7ec Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:13:39 +0200 Subject: [PATCH 16/46] fix --- horde/bridge_reference.py | 2 +- horde/classes/stable/worker.py | 2 +- tests/test_image.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/horde/bridge_reference.py b/horde/bridge_reference.py index 7c9ff85c..68eac7bf 100644 --- a/horde/bridge_reference.py +++ b/horde/bridge_reference.py @@ -186,7 +186,7 @@ def parse_bridge_agent(bridge_agent): @logger.catch(reraise=True) def check_bridge_capability(capability, bridge_agent): bridge_name, bridge_version = parse_bridge_agent(bridge_agent) - logger.debug([bridge_name, bridge_version]) + # logger.debug([bridge_name, bridge_version]) if bridge_name not in BRIDGE_CAPABILITIES: return False total_capabilities = set() diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 68ad1904..d0072fa7 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -157,7 +157,7 @@ def can_generate(self, waiting_prompt): if not waiting_prompt.safe_ip and not self.allow_unsafe_ipaddr: return [False, "unsafe_ip"] if self.limit_max_steps: - for mn in waiting_prompt.model_names(): + for mn in waiting_prompt.get_model_names(): avg_steps = ( int( model_reference.get_model_requirements(mn).get("min_steps", 20) diff --git a/tests/test_image.py b/tests/test_image.py index b1a17407..1d7dab13 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -116,7 +116,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "sampler_name": "k_euler", }, "models": TEST_MODELS_FLUX, - # "extra_slow_workers": True, + "extra_slow_workers": True, } protocol = "http" if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: @@ -129,7 +129,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: pop_dict = { "name": "CICD Fake Dreamer", "models": TEST_MODELS_FLUX, - "bridge_agent": "AI Horde Worker reGen:9.0.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "bridge_agent": "AI Horde Worker reGen:9.1.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, @@ -140,8 +140,8 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "allow_controlnet": True, "allow_sdxl_controlnet": True, "allow_lora": True, - # "extra_slow_worker": True, - # "limit_max_steps": True, + "extra_slow_worker": True, + "limit_max_steps": True, } pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: From 541d32a986c5d408c4ce05ccc6f1d33e469acf9f Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:19:39 +0200 Subject: [PATCH 17/46] test --- horde/apis/v2/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index 5278e2ed..2f2d4b61 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -456,7 +456,7 @@ def post(self): # as they're typically countermeasures to raids if skipped_reason != "secret": self.skipped[skipped_reason] = self.skipped.get(skipped_reason, 0) + 1 - # logger.warning(datetime.utcnow()) + logger.debug(self.skipped) continue # There is a chance that by the time we finished all the checks, another worker picked up the WP. @@ -477,7 +477,7 @@ def post(self): # We report maintenance exception only if we couldn't find any jobs if self.worker.maintenance: raise e.WorkerMaintenance(self.worker.maintenance_msg) - # logger.warning(datetime.utcnow()) + logger.debug(self.skipped) return {"id": None, "ids": [], "skipped": self.skipped}, 200 def get_sorted_wp(self, priority_user_ids=None): From 39d158510da933d6c5ee055a41a8f2c9a6383dbe Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:22:02 +0200 Subject: [PATCH 18/46] fix --- horde/apis/v2/base.py | 2 -- horde/apis/v2/stable.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index 2f2d4b61..f35d0a2b 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -456,7 +456,6 @@ def post(self): # as they're typically countermeasures to raids if skipped_reason != "secret": self.skipped[skipped_reason] = self.skipped.get(skipped_reason, 0) + 1 - logger.debug(self.skipped) continue # There is a chance that by the time we finished all the checks, another worker picked up the WP. @@ -477,7 +476,6 @@ def post(self): # We report maintenance exception only if we couldn't find any jobs if self.worker.maintenance: raise e.WorkerMaintenance(self.worker.maintenance_msg) - logger.debug(self.skipped) return {"id": None, "ids": [], "skipped": self.skipped}, 200 def get_sorted_wp(self, priority_user_ids=None): diff --git a/horde/apis/v2/stable.py b/horde/apis/v2/stable.py index 122887fc..dbdb9bcf 100644 --- a/horde/apis/v2/stable.py +++ b/horde/apis/v2/stable.py @@ -600,6 +600,8 @@ def post(self): db_skipped["kudos"] = post_ret["skipped"]["kudos"] if "blacklist" in post_ret.get("skipped", {}): db_skipped["blacklist"] = post_ret["skipped"]["blacklist"] + if "step_count" in post_ret.get("skipped", {}): + db_skipped["step_count"] = post_ret["skipped"]["step_count"] post_ret["skipped"] = db_skipped # logger.debug(post_ret) return post_ret, retcode From 9e7fdcbabbf23a5b51de6c4cfaacabb0702464da Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:32:24 +0200 Subject: [PATCH 19/46] extra slow workers count --- horde/database/functions.py | 6 ++++++ tests/test_image.py | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 30997443..6f90c7c8 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1054,6 +1054,12 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us ).count() if skipped_wps > 0: ret_dict["performance"] = skipped_wps + if worker.extra_slow_worker is True: + skipped_wps = open_wp_list.filter( + ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 + ).count() + if skipped_wps > 0: + ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps # Count skipped WPs requiring trusted workers if worker.user.trusted is False: skipped_wps = open_wp_list.filter( diff --git a/tests/test_image.py b/tests/test_image.py index 1d7dab13..be6bafc2 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -116,7 +116,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "sampler_name": "k_euler", }, "models": TEST_MODELS_FLUX, - "extra_slow_workers": True, + # "extra_slow_workers": True, } protocol = "http" if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: @@ -140,9 +140,10 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "allow_controlnet": True, "allow_sdxl_controlnet": True, "allow_lora": True, - "extra_slow_worker": True, + "extra_slow_worker": False, "limit_max_steps": True, } + # Test limit_max_steps pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: print(pop_req.text) @@ -153,6 +154,35 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: raise err pop_results = pop_req.json() print(json.dumps(pop_results, indent=4)) + try: + assert pop_results["id"] is None, pop_results + assert pop_results["skipped"]["step_count"] == 1, pop_results + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + # Test extra_slow_worker + pop_dict["limit_max_steps"] = False + pop_dict["extra_slow_worker"] = True + pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) + try: + print(pop_req.text) + assert pop_req.ok, pop_req.text + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + pop_results = pop_req.json() + print(json.dumps(pop_results, indent=4)) + try: + assert pop_results["id"] is None, pop_results + assert pop_results["skipped"]["step_count"] == 1, pop_results + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + job_id = pop_results["id"] try: From b98922a3358f292ebcbb8d254df0177df9493734 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:38:50 +0200 Subject: [PATCH 20/46] test --- horde/database/functions.py | 1 + tests/test_image.py | 23 +++++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 6f90c7c8..26ec995c 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1058,6 +1058,7 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us skipped_wps = open_wp_list.filter( ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 ).count() + logger.debug(skipped_wps) if skipped_wps > 0: ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps # Count skipped WPs requiring trusted workers diff --git a/tests/test_image.py b/tests/test_image.py index be6bafc2..fcb04e6a 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -143,6 +143,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: "extra_slow_worker": False, "limit_max_steps": True, } + # Test limit_max_steps pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: @@ -162,6 +163,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: print("Request cancelled") raise err requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + # Test extra_slow_worker pop_dict["limit_max_steps"] = False pop_dict["extra_slow_worker"] = True @@ -177,13 +179,30 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: print(json.dumps(pop_results, indent=4)) try: assert pop_results["id"] is None, pop_results - assert pop_results["skipped"]["step_count"] == 1, pop_results + assert pop_results["skipped"]["performance"] == 1, pop_results except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) print("Request cancelled") raise err + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) - + # Try popping as an extra slow worker + async_dict["extra_slow_workers"] = True + async_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/async", json=async_dict, headers=headers) + assert async_req.ok, async_req.text + async_results = async_req.json() + req_id = async_results["id"] + print(async_results) + pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) + try: + print(pop_req.text) + assert pop_req.ok, pop_req.text + except AssertionError as err: + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") + raise err + pop_results = pop_req.json() + print(json.dumps(pop_results, indent=4)) job_id = pop_results["id"] try: assert job_id is not None, pop_results From ae80b12a3b50ef312ed8d65f7074dadbce541cd4 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:40:00 +0200 Subject: [PATCH 21/46] test --- horde/database/functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 26ec995c..25fd9279 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1058,7 +1058,7 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us skipped_wps = open_wp_list.filter( ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 ).count() - logger.debug(skipped_wps) + logger.debug(open_wp_list.all()) if skipped_wps > 0: ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps # Count skipped WPs requiring trusted workers From dc7895e79f3b68e5236c9c1d89cba61b6d98639f Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:41:53 +0200 Subject: [PATCH 22/46] test --- horde/database/functions.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 25fd9279..3f411f3a 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1058,7 +1058,17 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us skipped_wps = open_wp_list.filter( ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 ).count() - logger.debug(open_wp_list.all()) + test = (db.session.query(ImageWaitingPrompt) + .options(noload(ImageWaitingPrompt.processing_gens)) + .outerjoin( + WPModels, + WPAllowedWorkers, + ) + .filter( + ImageWaitingPrompt.n > 0, + ) + ) + logger.debug(test.all()) if skipped_wps > 0: ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps # Count skipped WPs requiring trusted workers From 7935adbb7c0f29517aa3e5ab993d778ad91eba05 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:42:20 +0200 Subject: [PATCH 23/46] test --- horde/database/functions.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index 3f411f3a..78c96eb6 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1064,9 +1064,6 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us WPModels, WPAllowedWorkers, ) - .filter( - ImageWaitingPrompt.n > 0, - ) ) logger.debug(test.all()) if skipped_wps > 0: From 64a102a265db537616d2eb48aae6b9e513090017 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 10:58:10 +0200 Subject: [PATCH 24/46] test --- horde/apis/v2/base.py | 1 + horde/database/functions.py | 8 -------- tests/test_image.py | 34 ++++++++++++++++------------------ 3 files changed, 17 insertions(+), 26 deletions(-) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index f35d0a2b..6b94635e 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -476,6 +476,7 @@ def post(self): # We report maintenance exception only if we couldn't find any jobs if self.worker.maintenance: raise e.WorkerMaintenance(self.worker.maintenance_msg) + logger.debug(self.skipped) return {"id": None, "ids": [], "skipped": self.skipped}, 200 def get_sorted_wp(self, priority_user_ids=None): diff --git a/horde/database/functions.py b/horde/database/functions.py index 78c96eb6..6f90c7c8 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -1058,14 +1058,6 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us skipped_wps = open_wp_list.filter( ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 ).count() - test = (db.session.query(ImageWaitingPrompt) - .options(noload(ImageWaitingPrompt.processing_gens)) - .outerjoin( - WPModels, - WPAllowedWorkers, - ) - ) - logger.debug(test.all()) if skipped_wps > 0: ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps # Count skipped WPs requiring trusted workers diff --git a/tests/test_image.py b/tests/test_image.py index fcb04e6a..cb6b3996 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -10,6 +10,7 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: + print("test_simple_image_gen") headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user async_dict = { "prompt": "a horde of cute stable robots in a sprawling server room repairing a massive mainframe", @@ -37,7 +38,6 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: async_results = async_req.json() req_id = async_results["id"] # print(async_results) - print(async_results) pop_dict = { "name": "CICD Fake Dreamer", "models": TEST_MODELS, @@ -55,14 +55,14 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: } pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: - print(pop_req.text) + # print(pop_req.text) assert pop_req.ok, pop_req.text except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) print("Request cancelled") raise err pop_results = pop_req.json() - print(json.dumps(pop_results, indent=4)) + # print(json.dumps(pop_results, indent=4)) job_id = pop_results["id"] try: @@ -84,7 +84,7 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: retrieve_req = requests.get(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) assert retrieve_req.ok, retrieve_req.text retrieve_results = retrieve_req.json() - print(json.dumps(retrieve_results, indent=4)) + # print(json.dumps(retrieve_results, indent=4)) assert len(retrieve_results["generations"]) == 1 gen = retrieve_results["generations"][0] assert len(gen["gen_metadata"]) == 0 @@ -94,15 +94,17 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert gen["state"] == "ok" assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) TEST_MODELS_FLUX = ["Flux.1-Schnell fp8 (Compact)"] def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: + print("test_flux_image_gen") headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user async_dict = { - "prompt": "a horde of cute stable robots in a sprawling server room repairing a massive mainframe", + "prompt": "a horde of cute flux robots in a sprawling server room repairing a massive mainframe", "nsfw": True, "censor_nsfw": False, "r2": True, @@ -125,7 +127,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert async_req.ok, async_req.text async_results = async_req.json() req_id = async_results["id"] - print(async_results) + # print(async_results) pop_dict = { "name": "CICD Fake Dreamer", "models": TEST_MODELS_FLUX, @@ -154,29 +156,28 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: print("Request cancelled") raise err pop_results = pop_req.json() - print(json.dumps(pop_results, indent=4)) + # print(json.dumps(pop_results, indent=4)) try: assert pop_results["id"] is None, pop_results - assert pop_results["skipped"]["step_count"] == 1, pop_results + assert pop_results["skipped"].get("step_count") == 1, pop_results except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) print("Request cancelled") raise err - requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) # Test extra_slow_worker - pop_dict["limit_max_steps"] = False + async_dict["params"]["steps"] = 5 pop_dict["extra_slow_worker"] = True pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: - print(pop_req.text) + # print(pop_req.text) assert pop_req.ok, pop_req.text except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) print("Request cancelled") raise err pop_results = pop_req.json() - print(json.dumps(pop_results, indent=4)) + # print(json.dumps(pop_results, indent=4)) try: assert pop_results["id"] is None, pop_results assert pop_results["skipped"]["performance"] == 1, pop_results @@ -192,17 +193,14 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert async_req.ok, async_req.text async_results = async_req.json() req_id = async_results["id"] - print(async_results) pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: - print(pop_req.text) assert pop_req.ok, pop_req.text except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) print("Request cancelled") raise err pop_results = pop_req.json() - print(json.dumps(pop_results, indent=4)) job_id = pop_results["id"] try: assert job_id is not None, pop_results @@ -223,7 +221,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: retrieve_req = requests.get(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) assert retrieve_req.ok, retrieve_req.text retrieve_results = retrieve_req.json() - print(json.dumps(retrieve_results, indent=4)) + # print(json.dumps(retrieve_results, indent=4)) assert len(retrieve_results["generations"]) == 1 gen = retrieve_results["generations"][0] assert len(gen["gen_metadata"]) == 0 @@ -233,9 +231,9 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert gen["state"] == "ok" assert retrieve_results["kudos"] > 1 assert retrieve_results["done"] is True - + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) if __name__ == "__main__": # "ci/cd#12285" - # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") From 64b21df31cf52a5a04c4ce32fd80e7b751c8d9cb Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:00:20 +0200 Subject: [PATCH 25/46] test --- horde/apis/v2/stable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/horde/apis/v2/stable.py b/horde/apis/v2/stable.py index dbdb9bcf..c7ac581a 100644 --- a/horde/apis/v2/stable.py +++ b/horde/apis/v2/stable.py @@ -602,6 +602,8 @@ def post(self): db_skipped["blacklist"] = post_ret["skipped"]["blacklist"] if "step_count" in post_ret.get("skipped", {}): db_skipped["step_count"] = post_ret["skipped"]["step_count"] + if "bridge_version" in post_ret.get("skipped", {}): + db_skipped["bridge_version"] = db_skipped.get("bridge_version",0) + post_ret["skipped"]["step_count"] post_ret["skipped"] = db_skipped # logger.debug(post_ret) return post_ret, retcode From daceb7ef4ffea254b2ab319febd2b75ff79d9e35 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:00:28 +0200 Subject: [PATCH 26/46] test --- horde/apis/v2/stable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/apis/v2/stable.py b/horde/apis/v2/stable.py index c7ac581a..0adb002d 100644 --- a/horde/apis/v2/stable.py +++ b/horde/apis/v2/stable.py @@ -603,7 +603,7 @@ def post(self): if "step_count" in post_ret.get("skipped", {}): db_skipped["step_count"] = post_ret["skipped"]["step_count"] if "bridge_version" in post_ret.get("skipped", {}): - db_skipped["bridge_version"] = db_skipped.get("bridge_version",0) + post_ret["skipped"]["step_count"] + db_skipped["bridge_version"] = db_skipped.get("bridge_version",0) + post_ret["skipped"]["bridge_version"] post_ret["skipped"] = db_skipped # logger.debug(post_ret) return post_ret, retcode From 5998aaa1b0a29be75cb99fbe6d27a5e5e450b0a6 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:02:02 +0200 Subject: [PATCH 27/46] test --- horde/apis/v2/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index 6b94635e..d4801bea 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -449,6 +449,7 @@ def post(self): # logger.warning(datetime.utcnow()) while len(self.prioritized_wp) > 0: for wp in self.prioritized_wp: + logger.debug(wp) check_gen = self.worker.can_generate(wp) if not check_gen[0]: skipped_reason = check_gen[1] From 30e05f4db324b2777cecf8b975a2976be82865e3 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:12:58 +0200 Subject: [PATCH 28/46] test --- horde/classes/stable/worker.py | 15 +++++++++++++ tests/test_image.py | 39 ++++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index d0072fa7..d57b86d5 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -91,18 +91,21 @@ def can_generate(self, waiting_prompt): self.bridge_agent, waiting_prompt.gen_payload.get("karras", False), ): + logger.debug("bridge_version") return [False, "bridge_version"] # logger.warning(datetime.utcnow()) if len(waiting_prompt.gen_payload.get("post_processing", [])) >= 1 and not check_bridge_capability( "post-processing", self.bridge_agent, ): + logger.debug("bridge_version") return [False, "bridge_version"] for pp in KNOWN_POST_PROCESSORS: if pp in waiting_prompt.gen_payload.get("post_processing", []) and not check_bridge_capability( pp, self.bridge_agent, ): + logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.source_image and not self.allow_img2img: return [False, "img2img"] @@ -112,47 +115,59 @@ def can_generate(self, waiting_prompt): ): return [False, "models"] if waiting_prompt.params.get("tiling") and not check_bridge_capability("tiling", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.params.get("return_control_map") and not check_bridge_capability( "return_control_map", self.bridge_agent, ): + logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.params.get("control_type"): if not check_bridge_capability("controlnet", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if not check_bridge_capability("image_is_control", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if not self.allow_controlnet: + logger.debug("bridge_version") return [False, "controlnet"] if waiting_prompt.params.get("workflow") == "qr_code": if not check_bridge_capability("controlnet", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if not check_bridge_capability("qr_code", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if "stable_diffusion_xl" in model_reference.get_all_model_baselines(self.get_model_names()) and not self.allow_sdxl_controlnet: return [False, "controlnet"] if waiting_prompt.params.get("hires_fix") and not check_bridge_capability("hires_fix", self.bridge_agent): + logger.debug("bridge_version") return [False, "bridge_version"] if ( waiting_prompt.params.get("hires_fix") and "stable_cascade" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability("stable_cascade_2pass", self.bridge_agent) ): + logger.debug("bridge_version") return [False, "bridge_version"] if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", self.bridge_agent ): + logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", self.bridge_agent, ): + logger.debug("bridge_version") return [False, "bridge_version"] if any(lora.get("is_version") for lora in waiting_prompt.params.get("loras", [])) and not check_bridge_capability( "lora_versions", self.bridge_agent, ): + logger.debug("bridge_version") return [False, "bridge_version"] if not waiting_prompt.safe_ip and not self.allow_unsafe_ipaddr: return [False, "unsafe_ip"] diff --git a/tests/test_image.py b/tests/test_image.py index cb6b3996..cbd5bee1 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -161,8 +161,8 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert pop_results["id"] is None, pop_results assert pop_results["skipped"].get("step_count") == 1, pop_results except AssertionError as err: - requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) - print("Request cancelled") + # requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + # print("Request cancelled") raise err # Test extra_slow_worker @@ -233,7 +233,38 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["done"] is True requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) +def quick_pop(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: + print("quick_pop") + headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user + protocol = "http" + if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: + protocol = "https" + # print(async_results) + pop_dict = { + "name": "CICD Fake Dreamer", + "models": TEST_MODELS_FLUX, + "bridge_agent": "AI Horde Worker reGen:9.1.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "nsfw": True, + "amount": 10, + "max_pixels": 4194304, + "allow_img2img": True, + "allow_painting": True, + "allow_unsafe_ipaddr": True, + "allow_post_processing": True, + "allow_controlnet": True, + "allow_sdxl_controlnet": True, + "allow_lora": True, + "extra_slow_worker": False, + "limit_max_steps": True, + } + + # Test limit_max_steps + pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) + print(pop_req.text) + + if __name__ == "__main__": # "ci/cd#12285" - test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") - test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.1.1") + # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") + # test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") + quick_pop("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") From 97dbfa1aedc2a45f02344be84dab6568fbbe96df Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:15:39 +0200 Subject: [PATCH 29/46] test --- horde/classes/stable/worker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index d57b86d5..621d21d9 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -155,7 +155,7 @@ def can_generate(self, waiting_prompt): if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", self.bridge_agent ): - logger.debug("bridge_version") + logger.debug(["bridge_version",self.bridge_agent]) return [False, "bridge_version"] if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", From b752f1f2cf5df53a319f18b5917da01435590dd1 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:17:41 +0200 Subject: [PATCH 30/46] test --- horde/classes/base/worker.py | 1 - tests/test_image.py | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index b2d74ade..be1f2e04 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -294,7 +294,6 @@ def check_in(self, **kwargs): # So that they have to stay up at least 10 mins to get uptime kudos self.last_reward_uptime = self.uptime self.last_check_in = datetime.utcnow() - db.session.commit() def get_human_readable_uptime(self): if self.uptime < 60: diff --git a/tests/test_image.py b/tests/test_image.py index cbd5bee1..25d16413 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -265,6 +265,6 @@ def quick_pop(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: if __name__ == "__main__": # "ci/cd#12285" - # test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") - # test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") + test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") + test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") quick_pop("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") From 727cba185e1bf1c7999ffbf4f028a91aa614dba2 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:23:41 +0200 Subject: [PATCH 31/46] test --- horde/classes/base/worker.py | 4 ---- horde/classes/kobold/worker.py | 6 +++++- horde/classes/stable/worker.py | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index be1f2e04..4917d9ec 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -262,10 +262,6 @@ def toggle_paused(self, is_paused_active): # This should be extended by each worker type def check_in(self, **kwargs): - # To avoid excessive commits, - # we only record new changes on the worker every 30 seconds - if (datetime.utcnow() - self.last_check_in).total_seconds() < 30 and (datetime.utcnow() - self.created).total_seconds() > 30: - return self.ipaddr = kwargs.get("ipaddr", None) self.bridge_agent = sanitize_string(kwargs.get("bridge_agent", "unknown:0:unknown")) self.threads = kwargs.get("threads", 1) diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index 4bd0cafe..e31e5dcc 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import json -from datetime import timedelta +from datetime import datetime, timedelta from sqlalchemy.dialects.postgresql import UUID @@ -46,6 +46,10 @@ class TextWorker(Worker): wtype = "text" def check_in(self, max_length, max_context_length, softprompts, **kwargs): + # To avoid excessive commits, + # we only record new changes on the worker every 30 seconds + if (datetime.utcnow() - self.last_check_in).total_seconds() < 10 and (datetime.utcnow() - self.created).total_seconds() > 10: + return super().check_in(**kwargs) self.max_length = max_length self.max_context_length = max_context_length diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 621d21d9..3fcc7377 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -2,6 +2,8 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later +from datetime import datetime, timedelta + from horde import exceptions as e from horde.bridge_reference import ( check_bridge_capability, @@ -33,6 +35,10 @@ class ImageWorker(Worker): wtype = "image" def check_in(self, max_pixels, **kwargs): + # To avoid excessive commits, + # we only record new changes on the worker every 10 seconds + if (datetime.utcnow() - self.last_check_in).total_seconds() < 10 and (datetime.utcnow() - self.created).total_seconds() > 10: + return super().check_in(**kwargs) if kwargs.get("max_pixels", 512 * 512) > 3072 * 3072: # FIXME #noqa SIM102 if not self.user.trusted: From ce6ada5fe43c504d68ca55146151a197edced2a9 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:28:32 +0200 Subject: [PATCH 32/46] test --- horde/apis/v2/base.py | 1 - horde/classes/base/worker.py | 4 ++++ horde/classes/kobold/worker.py | 4 ---- horde/classes/stable/worker.py | 4 ---- tests/test_image.py | 6 +++--- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index d4801bea..6b94635e 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -449,7 +449,6 @@ def post(self): # logger.warning(datetime.utcnow()) while len(self.prioritized_wp) > 0: for wp in self.prioritized_wp: - logger.debug(wp) check_gen = self.worker.can_generate(wp) if not check_gen[0]: skipped_reason = check_gen[1] diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index 4917d9ec..cb5cdbd4 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -272,6 +272,10 @@ def check_in(self, **kwargs): self.prioritized_users = kwargs.get("prioritized_users", []) if not kwargs.get("safe_ip", True) and not self.user.trusted: self.report_suspicion(reason=Suspicions.UNSAFE_IP) + # To avoid excessive commits, + # we only record new uptime on the worker every 30 seconds + if (datetime.utcnow() - self.last_check_in).total_seconds() < 30 and (datetime.utcnow() - self.created).total_seconds() > 30: + return if not self.is_stale() and not self.paused and not self.maintenance: self.uptime += (datetime.utcnow() - self.last_check_in).total_seconds() # Every 10 minutes of uptime gets 100 kudos rewarded diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index e31e5dcc..857692e6 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -46,10 +46,6 @@ class TextWorker(Worker): wtype = "text" def check_in(self, max_length, max_context_length, softprompts, **kwargs): - # To avoid excessive commits, - # we only record new changes on the worker every 30 seconds - if (datetime.utcnow() - self.last_check_in).total_seconds() < 10 and (datetime.utcnow() - self.created).total_seconds() > 10: - return super().check_in(**kwargs) self.max_length = max_length self.max_context_length = max_context_length diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 3fcc7377..132ab6ff 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -35,10 +35,6 @@ class ImageWorker(Worker): wtype = "image" def check_in(self, max_pixels, **kwargs): - # To avoid excessive commits, - # we only record new changes on the worker every 10 seconds - if (datetime.utcnow() - self.last_check_in).total_seconds() < 10 and (datetime.utcnow() - self.created).total_seconds() > 10: - return super().check_in(**kwargs) if kwargs.get("max_pixels", 512 * 512) > 3072 * 3072: # FIXME #noqa SIM102 if not self.user.trusted: diff --git a/tests/test_image.py b/tests/test_image.py index 25d16413..1b569bfb 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -41,7 +41,7 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: pop_dict = { "name": "CICD Fake Dreamer", "models": TEST_MODELS, - "bridge_agent": "AI Horde Worker reGen:8.0.1-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "bridge_agent": "AI Horde Worker reGen:9.0.1-citests:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, @@ -131,7 +131,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: pop_dict = { "name": "CICD Fake Dreamer", "models": TEST_MODELS_FLUX, - "bridge_agent": "AI Horde Worker reGen:9.1.0-citests:https://github.com/Haidra-Org/horde-worker-reGen", + "bridge_agent": "AI Horde Worker reGen:9.0.1-citests:https://github.com/Haidra-Org/horde-worker-reGen", "nsfw": True, "amount": 10, "max_pixels": 4194304, @@ -267,4 +267,4 @@ def quick_pop(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: # "ci/cd#12285" test_simple_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") test_flux_image_gen("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") - quick_pop("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") + # quick_pop("2bc5XkMeLAWiN9O5s7bhfg", "dev.stablehorde.net", "0.2.0") From ef38f73f39deb1a6111c59d56036afc9d6e24e7c Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:35:13 +0200 Subject: [PATCH 33/46] flush --- horde/classes/base/worker.py | 34 +++++++++++++++++----------------- horde/classes/kobold/worker.py | 5 +++-- horde/classes/stable/worker.py | 2 +- tests/test_image.py | 4 ++-- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index cb5cdbd4..f8bf2ef8 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -197,7 +197,7 @@ def report_suspicion(self, amount=1, reason=Suspicions.WORKER_PROFANITY, formats f"Last suspicion log: {reason.name}.\n" f"Total Suspicion {self.get_suspicion()}", ) - db.session.commit() + db.session.flush() def get_suspicion_reasons(self): return set([s.suspicion_id for s in self.suspicions]) @@ -205,7 +205,7 @@ def get_suspicion_reasons(self): def reset_suspicion(self): """Clears the worker's suspicion and resets their reasons""" db.session.query(WorkerSuspicions).filter_by(worker_id=self.id).delete() - db.session.commit() + db.session.flush() def get_suspicion(self): return len(self.suspicions) @@ -226,7 +226,7 @@ def set_name(self, new_name): if len(new_name) > 100: return "Too Long" self.name = sanitize_string(new_name) - db.session.commit() + db.session.flush() return "OK" def set_info(self, new_info): @@ -237,12 +237,12 @@ def set_info(self, new_info): if len(new_info) > 1000: return "Too Long" self.info = sanitize_string(new_info) - db.session.commit() + db.session.flush() return "OK" def set_team(self, new_team): self.team_id = new_team.id - db.session.commit() + db.session.flush() return "OK" # This should be overwriten by each specific horde @@ -254,11 +254,11 @@ def toggle_maintenance(self, is_maintenance_active, maintenance_msg=None): self.maintenance_msg = self.default_maintenance_msg if self.maintenance and maintenance_msg not in [None, ""]: self.maintenance_msg = sanitize_string(maintenance_msg) - db.session.commit() + db.session.flush() def toggle_paused(self, is_paused_active): self.paused = is_paused_active - db.session.commit() + db.session.flush() # This should be extended by each worker type def check_in(self, **kwargs): @@ -343,7 +343,7 @@ def record_contribution(self, raw_things, kudos, things_per_sec): ).delete(synchronize_session=False) new_performance = WorkerPerformance(worker_id=self.id, performance=things_per_sec) db.session.add(new_performance) - db.session.commit() + db.session.flush() if things_per_sec / hv.thing_divisors[self.wtype] > hv.suspicion_thresholds[self.wtype]: self.report_suspicion( reason=Suspicions.UNREASONABLY_FAST, @@ -356,10 +356,10 @@ def modify_kudos(self, kudos, action="generated"): if not kudos_details: kudos_details = WorkerStats(worker_id=self.id, action=action, value=round(kudos, 2)) db.session.add(kudos_details) - db.session.commit() + db.session.flush() else: kudos_details.value = round(kudos_details.value + kudos, 2) - db.session.commit() + db.session.flush() logger.trace([kudos_details, kudos_details.value]) def log_aborted_job(self): @@ -391,7 +391,7 @@ def log_aborted_job(self): self.report_suspicion(reason=Suspicions.TOO_MANY_JOBS_ABORTED) self.aborted_jobs = 0 self.uncompleted_jobs += 1 - db.session.commit() + db.session.flush() # def is_slow(self): @@ -430,19 +430,19 @@ def import_kudos_details(self, kudos_details): for key in kudos_details: new_kd = WorkerStats(worker_id=self.id, action=key, value=kudos_details[key]) db.session.add(new_kd) - db.session.commit() + db.session.flush() def import_performances(self, performances): for p in performances: new_kd = WorkerPerformance(worker_id=self.id, performance=p) db.session.add(new_kd) - db.session.commit() + db.session.flush() def import_suspicions(self, suspicions): for s in suspicions: new_suspicion = WorkerSuspicions(worker_id=self.id, suspicion_id=int(s)) db.session.add(new_suspicion) - db.session.commit() + db.session.flush() # Should be extended by each specific horde @logger.catch(reraise=True) @@ -528,7 +528,7 @@ def set_blacklist(self, blacklist): for word in blacklist: blacklisted_word = WorkerBlackList(worker_id=self.id, word=word[0:15]) db.session.add(blacklisted_word) - db.session.commit() + db.session.flush() def refresh_model_cache(self): models_list = [m.model for m in self.models] @@ -564,11 +564,11 @@ def set_models(self, models): return # logger.debug([existing_model_names,models, existing_model_names == models]) db.session.query(WorkerModel).filter_by(worker_id=self.id).delete() - db.session.commit() + db.session.flush() for model_name in models: model = WorkerModel(worker_id=self.id, model=model_name) db.session.add(model) - db.session.commit() + db.session.flush() self.refresh_model_cache() def parse_models(self, models): diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index 857692e6..a52c8f71 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -57,6 +57,7 @@ def check_in(self, max_length, max_context_length, softprompts, **kwargs): f"{paused_string}Text Worker {self.name} checked-in, offering models {self.models} " f"at {self.max_length} max tokens and {self.max_context_length} max content length.", ) + db.session.flush() def refresh_softprompt_cache(self): softprompts_list = [s.softprompt for s in self.softprompts] @@ -100,11 +101,11 @@ def set_softprompts(self, softprompts): ], ) db.session.query(TextWorkerSoftprompts).filter_by(worker_id=self.id).delete() - db.session.commit() + db.session.flush() for softprompt_name in softprompts: softprompt = TextWorkerSoftprompts(worker_id=self.id, softprompt=softprompt_name) db.session.add(softprompt) - db.session.commit() + db.session.flush() self.refresh_softprompt_cache() def calculate_uptime_reward(self): diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 132ab6ff..4dfe9609 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -52,7 +52,7 @@ def check_in(self, max_pixels, **kwargs): paused_string = "" if self.paused: paused_string = "(Paused) " - db.session.commit() + db.session.flush() logger.trace( f"{paused_string}Stable Worker {self.name} checked-in, offering models {self.get_model_names()} " f"at {self.max_pixels} max pixels", diff --git a/tests/test_image.py b/tests/test_image.py index 1b569bfb..a9476ad5 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -161,8 +161,8 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert pop_results["id"] is None, pop_results assert pop_results["skipped"].get("step_count") == 1, pop_results except AssertionError as err: - # requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) - # print("Request cancelled") + requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + print("Request cancelled") raise err # Test extra_slow_worker From e7d96bd6d83b33eb8ffe3b60fbe8bc15c8170bc4 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:46:22 +0200 Subject: [PATCH 34/46] bad use of flush --- horde/classes/base/worker.py | 27 +++++++++++++-------------- horde/classes/kobold/worker.py | 3 +-- horde/classes/stable/worker.py | 2 +- tests/test_image.py | 2 +- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index f8bf2ef8..0fd2af65 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -205,7 +205,7 @@ def get_suspicion_reasons(self): def reset_suspicion(self): """Clears the worker's suspicion and resets their reasons""" db.session.query(WorkerSuspicions).filter_by(worker_id=self.id).delete() - db.session.flush() + db.session.commit() def get_suspicion(self): return len(self.suspicions) @@ -226,7 +226,7 @@ def set_name(self, new_name): if len(new_name) > 100: return "Too Long" self.name = sanitize_string(new_name) - db.session.flush() + db.session.commit() return "OK" def set_info(self, new_info): @@ -237,12 +237,12 @@ def set_info(self, new_info): if len(new_info) > 1000: return "Too Long" self.info = sanitize_string(new_info) - db.session.flush() + db.session.commit() return "OK" def set_team(self, new_team): self.team_id = new_team.id - db.session.flush() + db.session.commit() return "OK" # This should be overwriten by each specific horde @@ -254,11 +254,11 @@ def toggle_maintenance(self, is_maintenance_active, maintenance_msg=None): self.maintenance_msg = self.default_maintenance_msg if self.maintenance and maintenance_msg not in [None, ""]: self.maintenance_msg = sanitize_string(maintenance_msg) - db.session.flush() + db.session.commit() def toggle_paused(self, is_paused_active): self.paused = is_paused_active - db.session.flush() + db.session.commit() # This should be extended by each worker type def check_in(self, **kwargs): @@ -343,7 +343,7 @@ def record_contribution(self, raw_things, kudos, things_per_sec): ).delete(synchronize_session=False) new_performance = WorkerPerformance(worker_id=self.id, performance=things_per_sec) db.session.add(new_performance) - db.session.flush() + db.session.commit() if things_per_sec / hv.thing_divisors[self.wtype] > hv.suspicion_thresholds[self.wtype]: self.report_suspicion( reason=Suspicions.UNREASONABLY_FAST, @@ -356,10 +356,10 @@ def modify_kudos(self, kudos, action="generated"): if not kudos_details: kudos_details = WorkerStats(worker_id=self.id, action=action, value=round(kudos, 2)) db.session.add(kudos_details) - db.session.flush() + db.session.commit() else: kudos_details.value = round(kudos_details.value + kudos, 2) - db.session.flush() + db.session.commit() logger.trace([kudos_details, kudos_details.value]) def log_aborted_job(self): @@ -391,7 +391,7 @@ def log_aborted_job(self): self.report_suspicion(reason=Suspicions.TOO_MANY_JOBS_ABORTED) self.aborted_jobs = 0 self.uncompleted_jobs += 1 - db.session.flush() + db.session.commit() # def is_slow(self): @@ -430,19 +430,19 @@ def import_kudos_details(self, kudos_details): for key in kudos_details: new_kd = WorkerStats(worker_id=self.id, action=key, value=kudos_details[key]) db.session.add(new_kd) - db.session.flush() + db.session.commit() def import_performances(self, performances): for p in performances: new_kd = WorkerPerformance(worker_id=self.id, performance=p) db.session.add(new_kd) - db.session.flush() + db.session.commit() def import_suspicions(self, suspicions): for s in suspicions: new_suspicion = WorkerSuspicions(worker_id=self.id, suspicion_id=int(s)) db.session.add(new_suspicion) - db.session.flush() + db.session.commit() # Should be extended by each specific horde @logger.catch(reraise=True) @@ -568,7 +568,6 @@ def set_models(self, models): for model_name in models: model = WorkerModel(worker_id=self.id, model=model_name) db.session.add(model) - db.session.flush() self.refresh_model_cache() def parse_models(self, models): diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index a52c8f71..bd333f36 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -57,7 +57,7 @@ def check_in(self, max_length, max_context_length, softprompts, **kwargs): f"{paused_string}Text Worker {self.name} checked-in, offering models {self.models} " f"at {self.max_length} max tokens and {self.max_context_length} max content length.", ) - db.session.flush() + db.session.commit() def refresh_softprompt_cache(self): softprompts_list = [s.softprompt for s in self.softprompts] @@ -105,7 +105,6 @@ def set_softprompts(self, softprompts): for softprompt_name in softprompts: softprompt = TextWorkerSoftprompts(worker_id=self.id, softprompt=softprompt_name) db.session.add(softprompt) - db.session.flush() self.refresh_softprompt_cache() def calculate_uptime_reward(self): diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 4dfe9609..132ab6ff 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -52,7 +52,7 @@ def check_in(self, max_pixels, **kwargs): paused_string = "" if self.paused: paused_string = "(Paused) " - db.session.flush() + db.session.commit() logger.trace( f"{paused_string}Stable Worker {self.name} checked-in, offering models {self.get_model_names()} " f"at {self.max_pixels} max pixels", diff --git a/tests/test_image.py b/tests/test_image.py index a9476ad5..191f4eb2 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -149,7 +149,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: # Test limit_max_steps pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: - print(pop_req.text) + # print(pop_req.text) assert pop_req.ok, pop_req.text except AssertionError as err: requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) From e1379755fecd533770ac4c9912cb3d02bfc54ebb Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:48:04 +0200 Subject: [PATCH 35/46] bad use of flush --- horde/apis/v2/base.py | 2 +- horde/classes/base/worker.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/horde/apis/v2/base.py b/horde/apis/v2/base.py index 6b94635e..0666b35f 100644 --- a/horde/apis/v2/base.py +++ b/horde/apis/v2/base.py @@ -476,7 +476,7 @@ def post(self): # We report maintenance exception only if we couldn't find any jobs if self.worker.maintenance: raise e.WorkerMaintenance(self.worker.maintenance_msg) - logger.debug(self.skipped) + # logger.debug(self.skipped) return {"id": None, "ids": [], "skipped": self.skipped}, 200 def get_sorted_wp(self, priority_user_ids=None): diff --git a/horde/classes/base/worker.py b/horde/classes/base/worker.py index 0fd2af65..8b3585ee 100644 --- a/horde/classes/base/worker.py +++ b/horde/classes/base/worker.py @@ -568,6 +568,7 @@ def set_models(self, models): for model_name in models: model = WorkerModel(worker_id=self.id, model=model_name) db.session.add(model) + db.session.commit() self.refresh_model_cache() def parse_models(self, models): From 7227bcc95b8e7d75dc1e0f80a686686e4b66d0a2 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:51:39 +0200 Subject: [PATCH 36/46] test --- horde/classes/base/waiting_prompt.py | 1 + 1 file changed, 1 insertion(+) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index 4fe84379..978d7a01 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -459,6 +459,7 @@ def abort_for_maintenance(self): logger.warning(f"Error when aborting WP. Skipping: {err}") def refresh(self, worker=None): + logger.debug([self.n,worker,worker.extra_slow_worker]) if self.n > 0 and worker is not None and worker.extra_slow_worker is True: self.expiry = get_extra_slow_expiry_date() else: From 9b78eb61d6693954671f1180741c3df73ecad7f3 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:52:41 +0200 Subject: [PATCH 37/46] test --- horde/classes/base/waiting_prompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index 978d7a01..266bed84 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -459,7 +459,7 @@ def abort_for_maintenance(self): logger.warning(f"Error when aborting WP. Skipping: {err}") def refresh(self, worker=None): - logger.debug([self.n,worker,worker.extra_slow_worker]) + logger.debug([self.n,worker,worker.extra_slow_worker if worker is not None else None]) if self.n > 0 and worker is not None and worker.extra_slow_worker is True: self.expiry = get_extra_slow_expiry_date() else: From beb69d3c757114ee9da98dbb34f846e04a427b54 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:53:33 +0200 Subject: [PATCH 38/46] test --- horde/classes/base/waiting_prompt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index 266bed84..d76d8b68 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -459,13 +459,13 @@ def abort_for_maintenance(self): logger.warning(f"Error when aborting WP. Skipping: {err}") def refresh(self, worker=None): - logger.debug([self.n,worker,worker.extra_slow_worker if worker is not None else None]) if self.n > 0 and worker is not None and worker.extra_slow_worker is True: self.expiry = get_extra_slow_expiry_date() else: new_expiry = get_expiry_date() if self.expiry < new_expiry: self.expiry = new_expiry + logger.debug([self.expiry,self.n,worker,worker.extra_slow_worker if worker is not None else None]) db.session.commit() def is_stale(self): From ef9ca968168797639b596b0b78e90a734357369c Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:56:22 +0200 Subject: [PATCH 39/46] test --- horde/classes/base/waiting_prompt.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/horde/classes/base/waiting_prompt.py b/horde/classes/base/waiting_prompt.py index d76d8b68..932e2159 100644 --- a/horde/classes/base/waiting_prompt.py +++ b/horde/classes/base/waiting_prompt.py @@ -459,13 +459,12 @@ def abort_for_maintenance(self): logger.warning(f"Error when aborting WP. Skipping: {err}") def refresh(self, worker=None): - if self.n > 0 and worker is not None and worker.extra_slow_worker is True: + if worker is not None and worker.extra_slow_worker is True: self.expiry = get_extra_slow_expiry_date() else: new_expiry = get_expiry_date() if self.expiry < new_expiry: self.expiry = new_expiry - logger.debug([self.expiry,self.n,worker,worker.extra_slow_worker if worker is not None else None]) db.session.commit() def is_stale(self): From d81082c73c7028f2d86b7f5490d5282574943bf4 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 11:59:27 +0200 Subject: [PATCH 40/46] lint --- horde/apis/models/kobold_v2.py | 4 +++- horde/apis/models/stable_v2.py | 4 +++- horde/apis/v2/stable.py | 2 +- horde/classes/kobold/worker.py | 2 +- horde/classes/stable/worker.py | 8 ++++---- horde/database/functions.py | 8 ++++---- horde/model_reference.py | 8 +++++--- tests/test_image.py | 4 ++-- 8 files changed, 23 insertions(+), 17 deletions(-) diff --git a/horde/apis/models/kobold_v2.py b/horde/apis/models/kobold_v2.py index 79403376..30a13062 100644 --- a/horde/apis/models/kobold_v2.py +++ b/horde/apis/models/kobold_v2.py @@ -350,7 +350,9 @@ def __init__(self, api): ), "extra_slow_workers": fields.Boolean( default=False, - description="When True, allows very slower workers to pick up this request. Use this when you don't mind waiting a lot.", + description=( + "When True, allows very slower workers to pick up this request. " "Use this when you don't mind waiting a lot." + ), ), }, ) diff --git a/horde/apis/models/stable_v2.py b/horde/apis/models/stable_v2.py index e3e7959b..cb101054 100644 --- a/horde/apis/models/stable_v2.py +++ b/horde/apis/models/stable_v2.py @@ -611,7 +611,9 @@ def __init__(self, api): ), "extra_slow_workers": fields.Boolean( default=False, - description="When True, allows very slower workers to pick up this request. Use this when you don't mind waiting a lot.", + description=( + "When True, allows very slower workers to pick up this request. " "Use this when you don't mind waiting a lot." + ), ), "censor_nsfw": fields.Boolean( default=False, diff --git a/horde/apis/v2/stable.py b/horde/apis/v2/stable.py index 0adb002d..cafba323 100644 --- a/horde/apis/v2/stable.py +++ b/horde/apis/v2/stable.py @@ -603,7 +603,7 @@ def post(self): if "step_count" in post_ret.get("skipped", {}): db_skipped["step_count"] = post_ret["skipped"]["step_count"] if "bridge_version" in post_ret.get("skipped", {}): - db_skipped["bridge_version"] = db_skipped.get("bridge_version",0) + post_ret["skipped"]["bridge_version"] + db_skipped["bridge_version"] = db_skipped.get("bridge_version", 0) + post_ret["skipped"]["bridge_version"] post_ret["skipped"] = db_skipped # logger.debug(post_ret) return post_ret, retcode diff --git a/horde/classes/kobold/worker.py b/horde/classes/kobold/worker.py index bd333f36..c6e2d9c8 100644 --- a/horde/classes/kobold/worker.py +++ b/horde/classes/kobold/worker.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import json -from datetime import datetime, timedelta +from datetime import timedelta from sqlalchemy.dialects.postgresql import UUID diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 132ab6ff..7de52a94 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -from datetime import datetime, timedelta from horde import exceptions as e from horde.bridge_reference import ( @@ -155,9 +154,10 @@ def can_generate(self, waiting_prompt): logger.debug("bridge_version") return [False, "bridge_version"] if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( - "flux", self.bridge_agent + "flux", + self.bridge_agent, ): - logger.debug(["bridge_version",self.bridge_agent]) + logger.debug(["bridge_version", self.bridge_agent]) return [False, "bridge_version"] if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", @@ -178,7 +178,7 @@ def can_generate(self, waiting_prompt): avg_steps = ( int( model_reference.get_model_requirements(mn).get("min_steps", 20) - + model_reference.get_model_requirements(mn).get("max_steps", 40) + + model_reference.get_model_requirements(mn).get("max_steps", 40), ) / 2 ) diff --git a/horde/database/functions.py b/horde/database/functions.py index 6f90c7c8..c9911a0d 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -841,10 +841,10 @@ def get_sorted_wp_filtered_to_worker(worker, models_list=None, blacklist=None, p ImageWaitingPrompt.slow_workers == True, # noqa E712 ), or_( - worker.extra_slow_worker == False, + worker.extra_slow_worker.is_(False), and_( - worker.extra_slow_worker == True, - ImageWaitingPrompt.extra_slow_workers == True, # noqa E712 + worker.extra_slow_worker.is_(True), + ImageWaitingPrompt.extra_slow_workers.is_(True), ), ), or_( @@ -1059,7 +1059,7 @@ def count_skipped_image_wp(worker, models_list=None, blacklist=None, priority_us ImageWaitingPrompt.extra_slow_workers == False, # noqa E712 ).count() if skipped_wps > 0: - ret_dict["performance"] = ret_dict.get("performance",0) + skipped_wps + ret_dict["performance"] = ret_dict.get("performance", 0) + skipped_wps # Count skipped WPs requiring trusted workers if worker.user.trusted is False: skipped_wps = open_wp_list.filter( diff --git a/horde/model_reference.py b/horde/model_reference.py index 0e45aa84..1f0a4279 100644 --- a/horde/model_reference.py +++ b/horde/model_reference.py @@ -3,9 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-or-later import os +from datetime import datetime import requests -from datetime import datetime from horde.logger import logger from horde.threads import PrimaryTimedFunction @@ -31,8 +31,10 @@ def call_function(self): for _riter in range(10): try: ref_json = "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/main/stable_diffusion.json" - if datetime.utcnow() <= datetime(2024, 9, 30): # Flux Beta - ref_json = "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/refs/heads/flux/stable_diffusion.json" + if datetime.utcnow() <= datetime(2024, 9, 30): # Flux Beta + ref_json = ( + "https://raw.githubusercontent.com/Haidra-Org/AI-Horde-image-model-reference/refs/heads/flux/stable_diffusion.json" + ) logger.debug("Using flux beta model reference...") self.reference = requests.get( os.getenv( diff --git a/tests/test_image.py b/tests/test_image.py index 191f4eb2..f31f121b 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -2,7 +2,6 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later -import json import requests @@ -96,8 +95,8 @@ def test_simple_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["done"] is True requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) -TEST_MODELS_FLUX = ["Flux.1-Schnell fp8 (Compact)"] +TEST_MODELS_FLUX = ["Flux.1-Schnell fp8 (Compact)"] def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: @@ -233,6 +232,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: assert retrieve_results["done"] is True requests.delete(f"{protocol}://{HORDE_URL}/api/v2/generate/status/{req_id}", headers=headers) + def quick_pop(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: print("quick_pop") headers = {"apikey": api_key, "Client-Agent": f"aihorde_ci_client:{CIVERSION}:(discord)db0#1625"} # ci/cd user From e79453f3c58894e48b291f950af98bdff692e483 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:00:25 +0200 Subject: [PATCH 41/46] lint --- horde/database/functions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/horde/database/functions.py b/horde/database/functions.py index c9911a0d..041ff949 100644 --- a/horde/database/functions.py +++ b/horde/database/functions.py @@ -841,9 +841,9 @@ def get_sorted_wp_filtered_to_worker(worker, models_list=None, blacklist=None, p ImageWaitingPrompt.slow_workers == True, # noqa E712 ), or_( - worker.extra_slow_worker.is_(False), + worker.extra_slow_worker is False, and_( - worker.extra_slow_worker.is_(True), + worker.extra_slow_worker is True, ImageWaitingPrompt.extra_slow_workers.is_(True), ), ), From ef065ef83b3d5de1184970fbbdd8d112041a4427 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:04:06 +0200 Subject: [PATCH 42/46] removed_debugs --- horde/classes/stable/worker.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index 7de52a94..cb11ccc9 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -92,21 +92,18 @@ def can_generate(self, waiting_prompt): self.bridge_agent, waiting_prompt.gen_payload.get("karras", False), ): - logger.debug("bridge_version") return [False, "bridge_version"] # logger.warning(datetime.utcnow()) if len(waiting_prompt.gen_payload.get("post_processing", [])) >= 1 and not check_bridge_capability( "post-processing", self.bridge_agent, ): - logger.debug("bridge_version") return [False, "bridge_version"] for pp in KNOWN_POST_PROCESSORS: if pp in waiting_prompt.gen_payload.get("post_processing", []) and not check_bridge_capability( pp, self.bridge_agent, ): - logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.source_image and not self.allow_img2img: return [False, "img2img"] @@ -116,42 +113,33 @@ def can_generate(self, waiting_prompt): ): return [False, "models"] if waiting_prompt.params.get("tiling") and not check_bridge_capability("tiling", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.params.get("return_control_map") and not check_bridge_capability( "return_control_map", self.bridge_agent, ): - logger.debug("bridge_version") return [False, "bridge_version"] if waiting_prompt.params.get("control_type"): if not check_bridge_capability("controlnet", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if not check_bridge_capability("image_is_control", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if not self.allow_controlnet: - logger.debug("bridge_version") return [False, "controlnet"] if waiting_prompt.params.get("workflow") == "qr_code": if not check_bridge_capability("controlnet", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if not check_bridge_capability("qr_code", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if "stable_diffusion_xl" in model_reference.get_all_model_baselines(self.get_model_names()) and not self.allow_sdxl_controlnet: return [False, "controlnet"] if waiting_prompt.params.get("hires_fix") and not check_bridge_capability("hires_fix", self.bridge_agent): - logger.debug("bridge_version") return [False, "bridge_version"] if ( waiting_prompt.params.get("hires_fix") and "stable_cascade" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability("stable_cascade_2pass", self.bridge_agent) ): - logger.debug("bridge_version") return [False, "bridge_version"] if "flux_1" in model_reference.get_all_model_baselines(self.get_model_names()) and not check_bridge_capability( "flux", @@ -163,13 +151,11 @@ def can_generate(self, waiting_prompt): "clip_skip", self.bridge_agent, ): - logger.debug("bridge_version") return [False, "bridge_version"] if any(lora.get("is_version") for lora in waiting_prompt.params.get("loras", [])) and not check_bridge_capability( "lora_versions", self.bridge_agent, ): - logger.debug("bridge_version") return [False, "bridge_version"] if not waiting_prompt.safe_ip and not self.allow_unsafe_ipaddr: return [False, "unsafe_ip"] From 2871b33ee5e6eefd1bf5a229fd15492e548f42e8 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:05:10 +0200 Subject: [PATCH 43/46] removed_debugs --- horde/classes/stable/worker.py | 1 - 1 file changed, 1 deletion(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index cb11ccc9..c1cefa19 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -145,7 +145,6 @@ def can_generate(self, waiting_prompt): "flux", self.bridge_agent, ): - logger.debug(["bridge_version", self.bridge_agent]) return [False, "bridge_version"] if waiting_prompt.params.get("clip_skip", 1) > 1 and not check_bridge_capability( "clip_skip", From e497e9e6c323b64308aa132a69c4b441161e9159 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:09:50 +0200 Subject: [PATCH 44/46] fix: not_nulls --- horde/classes/stable/worker.py | 14 +++++++------- sql_statements/4.43.0.txt | 15 ++++++++++----- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/horde/classes/stable/worker.py b/horde/classes/stable/worker.py index c1cefa19..22468869 100644 --- a/horde/classes/stable/worker.py +++ b/horde/classes/stable/worker.py @@ -24,13 +24,13 @@ class ImageWorker(Worker): } # TODO: Switch to max_power max_pixels = db.Column(db.BigInteger, default=512 * 512, nullable=False) - allow_img2img = db.Column(db.Boolean, default=True, nullable=False) - allow_painting = db.Column(db.Boolean, default=True, nullable=False) - allow_post_processing = db.Column(db.Boolean, default=True, nullable=False) - allow_controlnet = db.Column(db.Boolean, default=False, nullable=False) - allow_sdxl_controlnet = db.Column(db.Boolean, default=False, nullable=False) - allow_lora = db.Column(db.Boolean, default=False, nullable=False) - limit_max_steps = db.Column(db.Boolean, default=False, nullable=False) + allow_img2img = db.Column(db.Boolean, default=True, nullable=False, index=True) + allow_painting = db.Column(db.Boolean, default=True, nullable=False, index=True) + allow_post_processing = db.Column(db.Boolean, default=True, nullable=False, index=True) + allow_controlnet = db.Column(db.Boolean, default=False, nullable=False, index=True) + allow_sdxl_controlnet = db.Column(db.Boolean, default=False, nullable=False, index=True) + allow_lora = db.Column(db.Boolean, default=False, nullable=False, index=True) + limit_max_steps = db.Column(db.Boolean, default=False, nullable=False, index=True) wtype = "image" def check_in(self, max_pixels, **kwargs): diff --git a/sql_statements/4.43.0.txt b/sql_statements/4.43.0.txt index b472e993..967b9ebe 100644 --- a/sql_statements/4.43.0.txt +++ b/sql_statements/4.43.0.txt @@ -1,8 +1,13 @@ -ALTER TABLE waiting_prompts ADD COLUMN extra_slow_workers BOOLEAN default false; -ALTER TABLE workers ADD COLUMN extra_slow_worker BOOLEAN default false; -ALTER TABLE workers ADD COLUMN limit_max_steps BOOLEAN default false; -ALTER TABLE processing_gens ADD COLUMN job_ttl BOOLEAN default false; +ALTER TABLE waiting_prompts ADD COLUMN extra_slow_workers BOOLEAN NOT NULL default false; +ALTER TABLE workers ADD COLUMN extra_slow_worker BOOLEAN NOT NULL default false; +ALTER TABLE workers ADD COLUMN limit_max_steps BOOLEAN NOT NULL default false; ALTER TABLE processing_gens ADD COLUMN job_ttl INTEGER NOT NULL DEFAULT 150; CREATE INDEX idx_processing_gens_job_ttl ON public.processing_gens USING btree(job_ttl); -CREATE INDEX idx_workers_extra_slow_worker ON public.workers USING btree(extra_slow_worker); CREATE INDEX idx_waiting_prompts_extra_slow_workers ON public.waiting_prompts USING btree(extra_slow_workers); +CREATE INDEX idx_workers_extra_slow_worker ON public.workers USING btree(extra_slow_worker); +CREATE INDEX idx_workers_allow_img2img ON public.workers USING btree(allow_img2img); +CREATE INDEX idx_workers_allow_painting ON public.workers USING btree(allow_painting); +CREATE INDEX idx_workers_allow_post_processing ON public.workers USING btree(allow_post_processing); +CREATE INDEX idx_workers_allow_controlnet ON public.workers USING btree(allow_controlnet); +CREATE INDEX idx_workers_allow_sdxl_controlnet ON public.workers USING btree(allow_sdxl_controlnet); +CREATE INDEX idx_workers_allow_lora ON public.workers USING btree(allow_lora); \ No newline at end of file From 5ccf1f9a888b118fda2e526ee071202a604a0c9e Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:12:35 +0200 Subject: [PATCH 45/46] avoid_timeouts_on_testys --- tests/test_image.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_image.py b/tests/test_image.py index f31f121b..9dee9266 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -import requests +import requests, time TEST_MODELS = ["Fustercluck", "AlbedoBase XL (SDXL)"] @@ -122,6 +122,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: protocol = "http" if HORDE_URL in ["dev.stablehorde.net", "stablehorde.net"]: protocol = "https" + time.sleep(1) async_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/async", json=async_dict, headers=headers) assert async_req.ok, async_req.text async_results = async_req.json() @@ -167,6 +168,7 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: # Test extra_slow_worker async_dict["params"]["steps"] = 5 pop_dict["extra_slow_worker"] = True + time.sleep(0.5) pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: # print(pop_req.text) @@ -188,10 +190,12 @@ def test_flux_image_gen(api_key: str, HORDE_URL: str, CIVERSION: str) -> None: # Try popping as an extra slow worker async_dict["extra_slow_workers"] = True + time.sleep(0.5) async_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/async", json=async_dict, headers=headers) assert async_req.ok, async_req.text async_results = async_req.json() req_id = async_results["id"] + time.sleep(0.5) pop_req = requests.post(f"{protocol}://{HORDE_URL}/api/v2/generate/pop", json=pop_dict, headers=headers) try: assert pop_req.ok, pop_req.text From aa4d0d1d8bc55df64795a8fd2b0dc55ed8a472c9 Mon Sep 17 00:00:00 2001 From: db0 Date: Fri, 13 Sep 2024 12:13:28 +0200 Subject: [PATCH 46/46] avoid_timeouts_on_testys --- tests/test_image.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_image.py b/tests/test_image.py index 9dee9266..48f35167 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -3,7 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-or-later -import requests, time +import time + +import requests TEST_MODELS = ["Fustercluck", "AlbedoBase XL (SDXL)"]