From 4ca310890f2c116fc5abbce3916b166ec4b71737 Mon Sep 17 00:00:00 2001 From: Kitty Cat Date: Thu, 14 Nov 2024 02:06:12 -0500 Subject: [PATCH] nushell: add vastai scripts --- nushell/vast.nu | 270 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 nushell/vast.nu diff --git a/nushell/vast.nu b/nushell/vast.nu new file mode 100644 index 0000000..9973811 --- /dev/null +++ b/nushell/vast.nu @@ -0,0 +1,270 @@ +# oof + +export def main [] { echo "corn waffle" } + + +export def "vast api-call get" [ + path: string # The path to make the API call to + query?: record # The data to scream at the server with +] { + # Assemble the URL to query + mut url_record = { + scheme: "https" + host: "console.vast.ai" + path: $"/api/v0/($path)/" + } + # Add the specified query, if specified + if $query != null { $url_record.query = ({ q: ($query | to json --raw)} | url build-query) } + return (http get --headers ['Authorization' $'Bearer (vast get-api-key)'] ($url_record | url join)) +} + +export def "vast api-call put" [ + path: string + data: record +] { + # Assemble the URL to query + let url_record = { + scheme: "https" + host: "console.vast.ai" + path: $"/api/v0/($path)/" + } + return (http put --headers ['Authorization' $'Bearer (vast get-api-key)'] --content-type application/json ($url_record | url join) ($data | to json --raw)) +} + + +# Retrieve the API key from the usual location +def "vast get-api-key" [] { + return (open ~/.vast_api_key | into string) +} + +# Search for instance types using custom query +export def "vast search offers" [] { + # Default parameters per vast client + mut params = { + verified: { eq: true } + external: { eq: false } + rentable: { eq: true } + rented: { eq: false } + order: [['score' 'desc']] + type: 'on-demand' + allocated_storage: 5.0 + } + # Custom desired default parameters that we're tacking on + $params.duration = { gte: 86400.0 } + $params.datacenter = { eq: true } + $params.inet_up_cost = { lte: 0.01 } + $params.inet_down_cost = { lte: 0.01 } + $params.num_gpus = { eq: 1 } + $params.gpu_ram = { gte: 8000.0 } + $params.cpu_cores_effective = { gte: 4 } + $params.cpu_ram = { gte: 8000.0 } + $params.inet_down = { gte: '400' } + $params.cuda_max_good = { gte: '12.1' } + $params.cpu_arch = { eq: 'amd64' } + $params.order = [['dph_total' 'asc']] + $params.allocated_storage = 100.0 + return ( + # vastai search offers --raw --storage 100.0 -o 'dph_total+' $'duration >= 1 datacenter = true verified = true rentable = true inet_up_cost <= 0.01 inet_down_cost <= 0.01 num_gpus = 1 gpu_ram >= ($mem) cpu_cores_effective >= 4 cpu_ram >= 8 inet_down >= 400 cuda_max_good >= 12.1 cpu_arch = amd64' + (vast api-call get bundles $params).offers | + update gpu_ram { |f| $f.gpu_ram * 1024 * 1024 | into filesize } | + update cpu_ram { |f| $f.cpu_ram * 1024 * 1024 | into filesize } | + select id gpu_name gpu_ram total_flops cpu_cores_effective cpu_ghz cpu_ram cuda_max_good geolocation dph_total inet_down inet_up | + rename index GPU VRAM FLOPS Cores GHz RAM CUDA Location Cost DL UL + ) +} +export alias vso = vast search offers + +export def "vast search offers completion" [] { + let data = (vast search offers) + let headers = ['GPU' 'VRAM'] + mut fdata = ($data | select index | rename value) + mut legend = $'(ansi white_bold)' + mut headerlen = {} + for x in ($data | columns) { + if $x == 'index' { continue } + print $'ok ($data | get $x | each { |it| str length $it } | max)' + } + return $headerlen +} + +#return ($data | select index GPU | rename value description | prepend { value: 'ID', description: $'(ansi white_bold)GPU'}) +#} + +export def "vast show instance" [ + id: int # The instance ID being requested + --nice # Clean up the output for human readability +] { + let data = ((vast api-call get $'instances/($id)').instances | rename --column {id: index}) + return $data +} + +export alias vsii = vast show instance + +export def "vast show instances" [ + --nice # Clean up the output for human readability +] { + let data = ((vast api-call get instances).instances | rename --column {id: index}) + if $nice { + return ($data + | select index machine_id actual_status num_gpus gpu_name gpu_util cpu_cores_effective cpu_ram disk_space ssh_host ssh_port dph_total image_uuid inet_up inet_down reliability2 label duration + | rename index Machine Status Num Model Util% vCPUs RAM Storage 'SSH Addr' 'SSH Port' '$/hr' Image 'Net up' 'Net Down' R Label Age ) } + return ($data | each { |x| sort }) +} + +export alias vsi = vast show instances + + +export def "test completion" [] { + return { + completions: [ + {value: 'x2 ok' description: "This is option 2" } + {value: 1 description: "This is option 1" } + {value: 3 description: "This is option 3" } + ], + options: { + case_sensitive: false + positional: false + completion_algorithm: "prefix" + sort: false + } + } +} + +export def test [arg: any@"test completion"] { return "ok!" } + +def "invokeenv tokens" [] { + mut output = [] + if "HUGGINGFACE_TOKEN" in $env { + $output ++= { url_regex: "huggingface.co" token: ($env.HUGGINGFACE_TOKEN) } } + if "CIVITAI_TOKEN" in $env { + $output ++= { url_regex: "civitai.com" token: ($env.CIVITAI_TOKEN) } } + return ($output | to json --raw) +} + +export def "invokeenv huggingface" [address: string] { + let inaddr = ($address | url parse) + let url_template = { + scheme: $inaddr.scheme + host: $inaddr.host + path: '/api/v2/models/hf_login' + port: $inaddr.port + } + http post ($url_template | url join) ({ token: $env.HUGGINGFACE_TOKEN } | to json --raw) +} + +export def "invokeenv models" [address: string models: list] { + let inaddr = ($address | url parse) + let url_template = { + scheme: $inaddr.scheme + host: $inaddr.host + path: '/api/v2/models/install' + port: $inaddr.port + query: '' + } + for model in $models { + (http post + ($url_template | update query ($model | select source inplace | update inplace { |x| into string} | url build-query) | url join) + ($model | reject source inplace | to json --raw)) + } +} + +export const invoke_models = [ + { name: 'WAI-ANI-NSFW-PONYXL v9.0' + description: 'Pony Diffusion v6 trained on additional NSFW materials.' + source: 'https://civitai.com/api/download/models/931577?type=Model&format=SafeTensor&size=pruned&fp=fp16' + base: 'sdxl' type: 'main' format: 'checkpoint' inplace: false + default_settings: { cfg_scale: 7 height: 1024 scheduler: 'dpmpp_2m_k' steps: 30 width: 1024 } } + { name: 'Age (-5 to 5)' + description: 'v1.0, for PonyXL, https://civitai.com/models/402667' + source: 'https://civitai.com/api/download/models/448977?type=Model&format=SafeTensor' + base: 'sdxl' type: 'lora' format: 'lycoris' inplace: false } + { name: 'Breast Size (0 to 5)' + description: 'v0.8, for PonyXL, https://civitai.com/models/549006' + source: 'https://civitai.com/api/download/models/610780?type=Model&format=SafeTensor' + base: 'sdxl' type: 'lora' format: 'lycoris' inplace: false } + { name: 'Darker Images' + description: 'v1.9, for PonyXL, https://civitai.com/models/883616' + source: 'https://civitai.com/api/download/models/989098?type=Model&format=SafeTensor' + base: 'sdxl' type: 'lora' format: 'lycoris' inplace: false + trigger_phrases: ['dark theme' 'dim lighting' 'black theme' 'silhouette' 'dark background' 'dark room' 'black background' 'backlighting'] } + { name: 'Hard Edge Detection (canny)' + description: 'Uses detected edges in the image to control composition.' + source: 'xinsir/controlNet-canny-sdxl-1.0' + base: 'sdxl' type: 'controlnet' format: 'diffusers' inplace: true } + { name: 'Tile' + description: 'Uses image data to replicate exact colors/structure in the resulting generation.' + source: 'xinsir/controlNet-tile-sdxl-1.0' + base: 'sdxl' type: 'controlnet' format: 'diffusers' inplace: true } + { name: 'Standard Reference (IP Adapter ViT-H)' + description: 'References images with a higher degree of precision.' + source: 'https://huggingface.co/InvokeAI/ip_adapter_sdxl_vit_h/resolve/main/ip-adapter_sdxl_vit-h.safetensors' + base: 'sdxl' type: 'ip_adapter' format: 'checkpoint' inplace: true } + { name: 'IP Adapter SDXL Image Encoder' + description: 'IP Adapter SDXL Image Encoder' + source: 'InvokeAI/ip_adapter_sdxl_image_encoder' + base: 'sdxl' type: 'clip_vision' format: 'diffusers' inplace: true } + { name: 'RealESRGAN_x2plus' + description: 'A Real-ESRGAN 2x upscaling model (general-purpose).' + source: 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth' + base: 'any' type: 'spandrel_image_to_image' format: null inplace: true } + { name: 'RealESRGAN_x4plus_anime_6B' + description: 'A Real-ESRGAN 4x upscaling model (optimized for anime images).' + source: 'https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth' + base: 'any' type: 'spandrel_image_to_image' format: null inplace: true } +] + +export def "vast run" [] {} +export def "vast run invokeai" [offer: int@"test completion", --vram: int] { + # Ensure that port 9090 isn't in use before we start + if (port 9090) != 9090 { error make -u { msg: "Port 9090 is currently in use." } } + #vastai create instance --explain --raw 13416877 --image ghcr.io/invoke-ai/invokeai --disk 100 --ssh --cancel-unavail --direct --onstart-cmd invokeai-web --env '-e INVOKEAI_RANDOM=25' + mut $req = { + client_id: 'me' + image: 'ghcr.io/invoke-ai/invokeai' + env: { + INVOKEAI_REMOTE_API_TOKENS: (invokeenv tokens) + INVOKEAI_LAZY_OFFLOAD: 'true' } + price: null + disk: 100.0 + label: null + extra: null + onstart: 'touch ~/.no_auto_tmux; invokeai-web' + image_login: null + python_utf8: false + lang_utf8: false + jupyter_dir: none + force: false + cancel_unavail: true + template_hash_id: null + runtype: 'ssh_direc ssh_proxy' + } + if $vram != null { $req.env.INVOKEAI_RAM = ($vram | into string) } + # Create the actual vast.ai instance + let instance_creation = (vast api-call put $'asks/($offer)' $req) + if $instance_creation.success != true { error make -u { msg: "Instance creation failed." } } + # Store our instance ID so that we can use it easily + let iid = $instance_creation.new_contract + print $'Instance created: ($iid)' + # Loop until the docker container has reached the "running" state + mut status = {actual_status: ''} + while $status.actual_status != 'running' { + sleep 5sec + $status = (vast show instance $iid) + print $'($status.actual_status): ($status.status_msg | str trim)' + } + print 'Waiting for tunnel...' + cmd /c $'start ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=NUL -o PasswordAuthentication=no -fN -L9090:localhost:9090 (vastai ssh-url ($iid))' + sleep 5sec + try { let sshpid = ((ps -l | where command =~ '-L9090:localhost:9090').0.pid) } catch { error make -u { msg: "SSH process has died." } } + print 'Waiting for InvokeAI...' + mut invoke_ready = false + while $invoke_ready == false { + try { + http get 'http://localhost:9090' + $invoke_ready = true + } catch { sleep 1sec } + } + invokeenv huggingface 'http://localhost:9090' + invokeenv models 'http://localhost:9090' $invoke_models + print 'Ready! http://localhost:9090' +}