#!/usr/bin/env python """ sd api txt2img benchmark """ import os import asyncio import base64 import io import json import time import argparse from PIL import Image import sdapi from util import Map, log oom = 0 args = None options = None async def txt2img(): t0 = time.perf_counter() data = {} try: data = await sdapi.post('/sdapi/v1/txt2img', options) except Exception: return -1 if 'error' in data: return -1 if 'info' in data: info = Map(json.loads(data['info'])) else: return 0 log.debug({ 'info': info }) if options['batch_size'] != len(data['images']): log.error({ 'requested': options['batch_size'], 'received': len(data['images']) }) return 0 for i in range(len(data['images'])): data['images'][i] = Image.open(io.BytesIO(base64.b64decode(data['images'][i].split(',',1)[0]))) if args.save: fn = os.path.join(args.save, f'benchmark-{i}-{len(data["images"])}.png') data["images"][i].save(fn) log.debug({ 'save': fn }) log.debug({ "images": data["images"] }) t1 = time.perf_counter() return t1 - t0 def memstats(): mem = sdapi.getsync('/sdapi/v1/memory') cpu = mem.get('ram', 'unavailable') gpu = mem.get('cuda', 'unavailable') if 'active' in gpu: gpu['session'] = gpu.pop('active') if 'reserved' in gpu: gpu.pop('allocated') gpu.pop('reserved') gpu.pop('inactive') if 'events' in gpu: global oom # pylint: disable=global-statement oom = gpu['events']['oom'] gpu.pop('events') return cpu, gpu def gb(val: float): return round(val / 1024 / 1024 / 1024, 2) async def main(): sdapi.quiet = True await sdapi.session() await sdapi.interrupt() ver = await sdapi.get("/sdapi/v1/version") log.info({ 'version': ver}) platform = await sdapi.get("/sdapi/v1/platform") log.info({ 'platform': platform }) opts = await sdapi.get('/sdapi/v1/options') opts = Map(opts) log.info({ 'model': opts.sd_model_checkpoint }) cpu, gpu = memstats() log.info({ 'system': { 'cpu': cpu, 'gpu': gpu }}) batch = [1, 1, 2, 4, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256] batch = [b for b in batch if b <= args.maxbatch] log.info({"batch-sizes": batch}) for i in range(len(batch)): if oom > 0: continue options['batch_size'] = batch[i] warmup = await txt2img() ts = await txt2img() if i == 0: ts += warmup if ts > 0.01: # cannot be faster than 10ms per run await asyncio.sleep(0) cpu, gpu = memstats() if i == 0: log.info({ 'warmup': round(ts, 2) }) else: peak = gpu['system']['used'] # gpu['session']['peak'] if 'session' in gpu else 0 log.info({ 'batch': batch[i], 'its': round(options.steps / (ts / batch[i]), 2), 'img': round(ts / batch[i], 2), 'wall': round(ts, 2), 'peak': gb(peak), 'oom': oom > 0 }) else: await asyncio.sleep(10) cpu, gpu = memstats() log.info({ 'batch': batch[i], 'result': 'error', 'gpu': gpu, 'oom': oom > 0 }) break if oom > 0: log.info({ 'benchmark': 'ended with oom so you should probably restart your automatic server now' }) await sdapi.close() if __name__ == '__main__': log.info({ 'run-benchmark' }) parser = argparse.ArgumentParser(description = 'run-benchmark') parser.add_argument("--steps", type=int, default=50, required=False, help="steps") parser.add_argument("--sampler", type=str, default='Euler a', required=False, help="Use specific sampler") parser.add_argument("--prompt", type=str, default='photo of two dice on a table', required=False, help="prompt") parser.add_argument("--negative", type=str, default='foggy, blurry', required=False, help="prompt") parser.add_argument("--maxbatch", type=int, default=16, required=False, help="max batch size") parser.add_argument("--width", type=int, default=512, required=False, help="width") parser.add_argument("--height", type=int, default=512, required=False, help="height") parser.add_argument('--debug', default = False, action='store_true', help = 'debug logging') parser.add_argument('--taesd', default = False, action='store_true', help = 'use taesd as vae') parser.add_argument("--save", type=str, default='', required=False, help="save images to folder") args = parser.parse_args() if args.debug: log.setLevel('DEBUG') options = Map( { "prompt": args.prompt, "negative_prompt": args.negative, "steps": args.steps, "sampler_name": args.sampler, "width": args.width, "height": args.height, "full_quality": not args.taesd, "cfg_scale": 0, "batch_size": 1, "n_iter": 1, "seed": -1, } ) log.info({"options": options}) try: asyncio.run(main()) except KeyboardInterrupt: log.warning({ 'interrupted': 'keyboard request' }) sdapi.interruptsync()