192 lines
6.8 KiB
Python
192 lines
6.8 KiB
Python
import socket
|
|
import struct
|
|
import threading
|
|
import json
|
|
import torch
|
|
import time
|
|
import folder_paths
|
|
import comfy.sd
|
|
import comfy.utils
|
|
|
|
class TensorEncoder(json.JSONEncoder):
|
|
def default(self, obj):
|
|
if isinstance(obj, torch.Tensor):
|
|
return {"__tensor__": True, "value": obj.cpu().tolist()}
|
|
return super().default(obj)
|
|
|
|
def tensor_hook(dct):
|
|
if "__tensor__" in dct:
|
|
return torch.tensor(dct["value"])
|
|
return dct
|
|
|
|
def log(msg):
|
|
print(f"[RemoteCLIP {time.strftime('%H:%M:%S')}] {msg}", flush=True)
|
|
|
|
def recv_exact(sock, size):
|
|
buf = b""
|
|
while len(buf) < size:
|
|
chunk = sock.recv(size - len(buf))
|
|
if not chunk:
|
|
raise ConnectionError("Socket closed")
|
|
buf += chunk
|
|
return buf
|
|
|
|
def send_packet(sock, meta, blob):
|
|
meta_bytes = json.dumps(meta, cls=TensorEncoder).encode("utf-8")
|
|
sock.sendall(struct.pack(">Q", len(meta_bytes)))
|
|
sock.sendall(meta_bytes)
|
|
sock.sendall(blob)
|
|
|
|
def recv_packet(sock):
|
|
meta_size = struct.unpack(">Q", recv_exact(sock, 8))[0]
|
|
meta = json.loads(recv_exact(sock, meta_size).decode("utf-8"), object_hook=tensor_hook)
|
|
blob = recv_exact(sock, meta.get("blob_size", 0))
|
|
return meta, blob
|
|
|
|
def pack_tensors(tensors):
|
|
meta = {}
|
|
blobs = []
|
|
for name, t in tensors.items():
|
|
if t is None:
|
|
log(f"Warning: tensor '{name}' is None, replacing with zeros")
|
|
if name == "cond":
|
|
t = torch.zeros((1, 77, 768))
|
|
else:
|
|
t = torch.zeros((1, 768))
|
|
t = t.detach().contiguous().cpu()
|
|
raw = t.numpy().tobytes()
|
|
meta[name] = {"dtype": str(t.dtype), "shape": list(t.shape), "size": len(raw)}
|
|
blobs.append(raw)
|
|
return meta, b"".join(blobs)
|
|
|
|
def unpack_tensors(meta, blob):
|
|
out = {}
|
|
offset = 0
|
|
for name, info in meta.items():
|
|
size = info["size"]
|
|
raw = bytearray(blob[offset:offset + size])
|
|
offset += size
|
|
dtype = getattr(torch, info["dtype"].split(".")[-1])
|
|
t = torch.frombuffer(raw, dtype=dtype).clone()
|
|
out[name] = t.reshape(info["shape"])
|
|
return out
|
|
|
|
class RemoteCLIPProxy:
|
|
def __init__(self, ip, port):
|
|
self.ip = ip
|
|
self.port = port
|
|
self.sock = socket.create_connection((ip, port), timeout=60)
|
|
log(f"Connected to worker {ip}:{port}")
|
|
|
|
def clone(self):
|
|
return self
|
|
|
|
def add_patches(self, *_, **__):
|
|
return self
|
|
|
|
def tokenize(self, text, **kwargs):
|
|
return {"text": text, "kwargs": kwargs}
|
|
|
|
def _infer(self, tokens):
|
|
meta = {"cmd": "encode", "text": tokens["text"], "kwargs": tokens["kwargs"], "blob_size": 0}
|
|
try:
|
|
log("Sending encode request")
|
|
send_packet(self.sock, meta, b"")
|
|
resp_meta, blob = recv_packet(self.sock)
|
|
tensors = unpack_tensors(resp_meta["tensors"], blob)
|
|
cond = tensors.get("cond", torch.zeros((1, 77, 768)))
|
|
pooled = tensors.get("pooled", torch.zeros((1, 768)))
|
|
log("Received embeddings")
|
|
return cond, pooled
|
|
except Exception as e:
|
|
log(f"Remote CLIP inference failed: {e}")
|
|
return torch.zeros((1, 77, 768)), torch.zeros((1, 768))
|
|
|
|
def encode_from_tokens(self, tokens, return_pooled=True, return_dict=False):
|
|
cond, pooled = self._infer(tokens)
|
|
if return_dict:
|
|
return {"cond": cond, "pooled_output": pooled}
|
|
return cond, pooled
|
|
|
|
def encode_from_tokens_scheduled(self, tokens):
|
|
cond, pooled = self._infer(tokens)
|
|
return [[cond, {"pooled_output": pooled}]]
|
|
|
|
class SendRemoteCLIP:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {"CLIP": ("CLIP",), "listen_port": ("INT", {"default": 8181})}}
|
|
|
|
RETURN_TYPES = ()
|
|
FUNCTION = "start_worker"
|
|
OUTPUT_NODE = True
|
|
CATEGORY = "Remote CLIP"
|
|
|
|
def start_worker(self, CLIP, listen_port):
|
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
server.bind(("0.0.0.0", listen_port))
|
|
server.listen(16)
|
|
log(f"Worker listening on port {listen_port}")
|
|
|
|
def handle(conn):
|
|
addr = conn.getpeername()
|
|
log(f"Client connected: {addr}")
|
|
with conn:
|
|
while True:
|
|
try:
|
|
meta, _ = recv_packet(conn)
|
|
if meta is None or "text" not in meta:
|
|
log("Warning: invalid request, skipping")
|
|
continue
|
|
log(f"Encoding prompt ({len(meta['text'])} chars)")
|
|
with torch.inference_mode():
|
|
tokens = CLIP.tokenize(meta["text"], **meta["kwargs"])
|
|
cond, pooled = CLIP.encode_from_tokens(tokens, return_pooled=True)
|
|
tensor_meta, blob = pack_tensors({"cond": cond, "pooled": pooled})
|
|
reply_meta = {"tensors": tensor_meta, "blob_size": len(blob)}
|
|
send_packet(conn, reply_meta, blob)
|
|
log(f"Sent embeddings ({len(blob)} bytes)")
|
|
except Exception as e:
|
|
log(f"Client disconnected: {e}")
|
|
break
|
|
|
|
while True:
|
|
conn, _ = server.accept()
|
|
threading.Thread(target=handle, args=(conn,), daemon=True).start()
|
|
|
|
class LoadRemoteCLIP:
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {"required": {"worker_ip": ("STRING", {"default": "127.0.0.1"}), "port": ("INT", {"default": 8181})}}
|
|
|
|
RETURN_TYPES = ("CLIP",)
|
|
FUNCTION = "load_remote"
|
|
CATEGORY = "Remote CLIP"
|
|
|
|
def load_remote(self, worker_ip, port):
|
|
return (RemoteCLIPProxy(worker_ip, port),)
|
|
|
|
class LoraLoaderCLIPOnly:
|
|
@classmethod
|
|
def INPUT_TYPES(s):
|
|
return {
|
|
"required": {
|
|
"CLIP": ("CLIP",),
|
|
"lora_name": (folder_paths.get_filename_list("loras"), ),
|
|
"strength_clip": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
|
}
|
|
}
|
|
|
|
RETURN_TYPES = ("CLIP",)
|
|
FUNCTION = "load_lora"
|
|
CATEGORY = "Remote CLIP"
|
|
|
|
def load_lora(self, CLIP, lora_name, strength_clip):
|
|
lora_path = folder_paths.get_full_path("loras", lora_name)
|
|
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
|
|
_, clip_lora = comfy.sd.load_lora_for_models(None, CLIP, lora, 0, strength_clip)
|
|
return (clip_lora,)
|
|
|
|
NODE_CLASS_MAPPINGS = {"SendRemoteCLIP": SendRemoteCLIP, "LoadRemoteCLIP": LoadRemoteCLIP, "LoraLoaderCLIPOnly": LoraLoaderCLIPOnly}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {"SendRemoteCLIP": "Send Remote CLIP", "LoadRemoteCLIP": "Load Remote CLIP", "LoraLoaderCLIPOnly": "LoraLoaderCLIPOnly"} |