From c73aa4b6c8c8a4383ee518aff46c741de5152dad Mon Sep 17 00:00:00 2001 From: stephen schuresko Date: Fri, 13 Mar 2026 12:41:32 -0400 Subject: [PATCH] initial commit --- create_payload.json | 31 ++++++++ m2.py | 39 ++++++++++ requirements.txt | 3 + search_params.json | 19 +++++ vast_start.py | 177 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 create_payload.json create mode 100644 m2.py create mode 100644 requirements.txt create mode 100644 search_params.json create mode 100644 vast_start.py diff --git a/create_payload.json b/create_payload.json new file mode 100644 index 0000000..c43f86b --- /dev/null +++ b/create_payload.json @@ -0,0 +1,31 @@ +{ + "template_hash_id": "38a9dab633743d43107eb9a80d4ada9e", + "client_id": "me", + "image": "vastai/ollama:0.17.5", + "env": { + "-p 1111:1111": "1", + "-p 6006:6006": "1", + "-p 7860:7860": "1", + "-p 8080:8080": "1", + "-p 8384:8384": "1", + "-p 72299:72299": "1", + "-p 21434:21434": "1", + "OLLAMA_MODEL": "qwen3.5:35b", + "PORTAL_CONFIG": "localhost:1111:11111:/:Instance Portal|localhost:7860:17860:/:Model UI|localhost:21434:11434:/:Ollama API|localhost:8080:18080:/:Jupyter|localhost:8080:8080:/terminals/1:Jupyter Terminal", + "OPEN_BUTTON_PORT": "1111", + "OPEN_BUTTON_TOKEN": "1", + "JUPYTER_DIR": "/", + "DATA_DIRECTORY": "/workspace/" + }, + "args_str": null, + "onstart": "entrypoint.sh", + "runtype": "jupyter_direc ssh_direc ssh_proxy", + "image_login": null, + "use_jupyter_lab": false, + "jupyter_dir": null, + "python_utf8": null, + "lang_utf8": null, + "disk": 32, + "last_known_min_bid": 0.6666666666666666, + "min_duration": 604800 +} \ No newline at end of file diff --git a/m2.py b/m2.py new file mode 100644 index 0000000..da96e4d --- /dev/null +++ b/m2.py @@ -0,0 +1,39 @@ +#!/bin/env python3 +import vast_start as vs + + +print("Searching cheapest 39GB+ GPU...") +offers = vs.get_cheapest_gpus() +instance_id=0 +for offer in offers: + offer_id = offer["id"] + print("Offer:", offer_id, "GPU:", offer["gpu_name"], "Price:", offer["dph_total"]) + + print("Creating instance...") + try: + for k in offer: + v=offer[k] + print(f"{k}: {v}") + instance_id = vs.create_instance(offer_id) + break + except: + print(f"failed to claim offer {offer_id}") +data=[] +if instance_id > 0 : + print("Instance:", instance_id) + data = vs.wait_until_ready(instance_id) + ip = data["ssh_host"] + port = data["ssh_port"] + user = "root" + #password = data["ssh_pwd"] + + print("Instance ready:", ip) + + vs.ssh_and_setup(ip, port, user, "") + + ip=data["public_ipaddr"] + port=data['ports']['22/tcp'][0]['HostPort'] + print(f"ssh root@{ip} -p {port}") + ollama_port=data['ports']['11434/tcp'][0]['HostPort'] + print(f"ollama up at http://{ip}:{ollama_port}") + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d934a82 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +vastai +paramiko +requests \ No newline at end of file diff --git a/search_params.json b/search_params.json new file mode 100644 index 0000000..f7443da --- /dev/null +++ b/search_params.json @@ -0,0 +1,19 @@ +{ + "verified": {"eq": true}, + "cuda_max_good": {"gt": "12", "lt":"13"}, + "external": {"eq": false}, + "rentable": {"eq": true}, + "rented": {"eq": false}, + "cpu_ram": {"gt":23}, + "gpu_ram": {"gt": 50000}, + "reliability": {"gt": ".99"}, + "direct": {"eq": true}, + "order": [ + [ + "price", + "asc" + ] + ], + "type": "on-demand", + "allocated_storage": 5.0 +} \ No newline at end of file diff --git a/vast_start.py b/vast_start.py new file mode 100644 index 0000000..a1f9603 --- /dev/null +++ b/vast_start.py @@ -0,0 +1,177 @@ +#!/bin/env python3 +import requests +import time +import paramiko +import json +import os +import subprocess +model="qwen3-coder-next:q8_0" +vram=80000 +max_bid=2.5 +space=int(vram/700+20) + +def load_api_key(): + path = os.path.join( + os.environ["HOME"], + ".config", + "vastai", + "vast_api_key" + ) + + with open(path, "r") as f: + return f.read().strip() + +def file_obj(path): + with open(path, "r") as f: + return json.load(f) + +API_KEY = load_api_key() + +BASE = "https://console.vast.ai/api/v0" +params = file_obj("search_params.json") +create_payload = file_obj("create_payload.json") +def vast_request(method, path, params=None, json_data=None): + url = f"{BASE}/{path}" + print(f"Request: {method} {url} params={params} json={json_data}") + headers = {"Authorization": f"Bearer {API_KEY}"} + r = requests.request(method, url, headers=headers, params=params, json=json_data) + print(f"Response: {r.status_code} {r.text}") + r.raise_for_status() + return r.json() + +def get_cheapest_gpus(): + params["gpu_ram"]["gt"] = vram + r = vast_request("GET", "bundles/", params=params) + offers = r["offers"] + good_offers = [] + if not offers: + raise Exception("No GPU offers found") + for offer in offers: + if offer["gpu_ram"] > vram \ + and offer["dph_total"] < max_bid \ + and offer["verification"] == "verified": + good_offers.append(offer) + if len(good_offers) == 0: + raise Exception("No matching GPU offers found") + return good_offers + +def create_instance(offer_id): + #create_payload['id'] = offer_id + create_payload['env']['OLLAMA_MODEL']=model + create_payload['disk']=space + r = vast_request("PUT", f"asks/{offer_id}/", json_data=create_payload) + + return r["new_contract"] + +def destry_instance(instance_id): + r = requests.delete( + f"{BASE}/instances/{instance_id}/", + headers={"Authorization": f"Bearer {API_KEY}"} + ) + +def get_instance(instance_id): + r = requests.get( + f"{BASE}/instances/{instance_id}/", + headers={"Authorization": f"Bearer {API_KEY}"} + ) + data = r.json() + return data["instances"] + + +def wait_until_ready(instance_id): + while True: + data = get_instance(instance_id) + for tag in [ + "actual_status", + "ssh_host", + "ssh_port", + "ssh_pwd", + "verification", + "uptime_mins", + "cur_state", + "direct_port_count", + "gpu_arch", + "gpu_total_ram", + "gpu_util", + "intended_status", + "next_state", + "gpu_lanes", + "gpu_name", + "public_ipaddr", + "ports", + ]: + try: + if tag in data: + print(f"{tag}: {data[tag]}") + except: + print(f"{tag}: Error") + print("Instance status:", data["actual_status"]) + #print("Instance data:", data) + if data["actual_status"] == "running": + return data + + print("Waiting for instance...\n\n") + time.sleep(15) + + +def ssh_and_setup(ip, port, user, password): + + commands = [ + f"ollama pull {model}", + "touch ~/.no_auto_tmux", + #"ollama rm qwen3.5:35b", + #"ollama pull lama3.1:70b", + ] + cmd2=["ssh","-t","-o","StrictHostKeyChecking=accept-new","-p",str(port),f"{user}@{ip}", "touch","~/.no_auto_tmux"] + subprocess.run(cmd2) + cmd2=["ssh","-t","-p",str(port),f"{user}@{ip}","ollama","pull",model] + print(' '.join(cmd2)) + subprocess.run(cmd2) + + + +def main(): + + print("Searching cheapest 39GB+ GPU...") + offers = get_cheapest_gpus() + instance_id=0 + for offer in offers: + offer_id = offer["id"] + print("Offer:", offer_id, "GPU:", offer["gpu_name"], "Price:", offer["dph_total"]) + + print("Creating instance...") + try: + for k in offer: + v=offer[k] + print(f"{k}: {v}") + instance_id = create_instance(offer_id) + break + except: + print(f"failed to claim offer {offer_id}") + data=[] + if instance_id > 0 : + print("Instance:", instance_id) + data = wait_until_ready(instance_id) + ip = data["ssh_host"] + port = data["ssh_port"] + user = "root" + #password = data["ssh_pwd"] + + print("Instance ready:", ip) + + + pub_ip=data["public_ipaddr"] + pub_port=data['ports']['22/tcp'][0]['HostPort'] + + ssh_and_setup(ip, port, user, "") + print(f"ssh root@{pub_ip} -p {pub_port}") + ollama_port=data['ports']['11434/tcp'][0]['HostPort'] + print(f"ollama up at http://{pub_ip}:{ollama_port} with mode {model}") + + cmd2=["ssh","-t","-p",str(pub_port),f"{user}@{pub_ip}","ollama","run",model] + print(' '.join(cmd2)) + subprocess.run(cmd2) + + +if __name__ == "__main__": + main() \ No newline at end of file