docker_controller.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. ## Copyright (C) 2024, Nicholas Carlini <nicholas@carlini.com>.
  2. ##
  3. ## This program is free software: you can redistribute it and/or modify
  4. ## it under the terms of the GNU General Public License as published by
  5. ## the Free Software Foundation, either version 3 of the License, or
  6. ## (at your option) any later version.
  7. ##
  8. ## This program is distributed in the hope that it will be useful,
  9. ## but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. ## GNU General Public License for more details.
  12. ##
  13. ## You should have received a copy of the GNU General Public License
  14. ## along with this program. If not, see <http://www.gnu.org/licenses/>.
  15. import asyncio
  16. import pickle
  17. import sys
  18. import time
  19. import tarfile
  20. import io
  21. import threading
  22. import signal
  23. import subprocess
  24. import pty
  25. import os
  26. import select
  27. import re
  28. import termios
  29. import struct
  30. import fcntl
  31. import random
  32. import json
  33. import re
  34. # DO NOT SET THIS FLAG TO TRUE UNLESS YOU ARE SURE YOU UNDERSTAND THE CONSEQUENCES
  35. # IT IS VERY DANGEROUS. YOU WILL BE DIRECTLY EVALUATING WHATEVER COMES OUT OF
  36. # A LANGUAGE MODEL DIRECTLY ON YOUR COMPUTER WITH NO SAFETY CHECKS.
  37. I_HAVE_BLIND_FAITH_IN_LLMS_AND_AM_OKAY_WITH_THEM_BRICKING_MY_MACHINE_OR_MAKING_THEM_HALT_AND_CATCH_FIRE = False
  38. BACKEND = json.load(open("config.json"))['container']
  39. def make_tar(files):
  40. file_like_object = io.BytesIO()
  41. tar = tarfile.TarFile(fileobj=file_like_object, mode='w')
  42. for file_name, file_content in files.items():
  43. tarinfo = tarfile.TarInfo(name=file_name)
  44. tarinfo.size = len(file_content)
  45. tarinfo.mtime = time.time()
  46. tar.addfile(tarinfo, io.BytesIO(file_content))
  47. tar.close()
  48. file_like_object.seek(0)
  49. return file_like_object
  50. if BACKEND == "docker":
  51. import docker
  52. def setup_docker(env):
  53. env.docker = docker.from_env()
  54. env.container = env.docker.containers.run("llm-benchmark-image", detach=True, tty=True, auto_remove=True)
  55. def stop_and_remove_container(client, container_id):
  56. # Stopping the container
  57. client.containers.get(container_id).stop()
  58. # Removing the container
  59. client.containers.get(container_id).remove()
  60. def async_kill_container(client, container):
  61. thread = threading.Thread(target=stop_and_remove_container, args=(client, container.id))
  62. thread.daemon = True
  63. thread.start()
  64. def safe_run(client, container, files, run_cmd):
  65. tarfile = make_tar(files)
  66. path = "/usr/src/app"
  67. container.put_archive(path, tarfile)
  68. exit_code, output = container.exec_run(run_cmd)
  69. return output
  70. elif BACKEND == "podman":
  71. def setup_docker(env):
  72. # Starting a container with Podman
  73. result = subprocess.run(["podman", "run", "-d", "-t", "llm-benchmark-image"], capture_output=True, text=True, check=True)
  74. env.container = result.stdout.strip()
  75. env.docker = "I AM USING PODMAN THIS IS NOT NEEDED"
  76. def stop_and_remove_podman_container(container_id):
  77. # Stopping the container
  78. subprocess.run(["podman", "container", "stop", container_id], check=True)
  79. # Removing the container
  80. subprocess.run(["podman", "container", "rm", container_id], check=True)
  81. def async_kill_container(client, container_id):
  82. thread = threading.Thread(target=stop_and_remove_podman_container, args=(container_id,))
  83. thread.daemon = True
  84. thread.start()
  85. def safe_run(client, container_id, files, run_cmd):
  86. tarfile = make_tar(files)
  87. # Create a temporary directory in the container to store files
  88. subprocess.run(["podman", "exec", container_id, "mkdir", "-p", "/usr/src/app"], check=True)
  89. # Copying files to the container
  90. r = random.randint(0, 1000000)
  91. with open('/tmp/archive%d.tar'%r, 'wb') as out_f:
  92. out_f.write(tarfile.getbuffer())
  93. time.sleep(.1)
  94. subprocess.run(["podman", "cp", "/tmp/archive%d.tar"%r, f"{container_id}:/usr/src/app"], check=True)
  95. time.sleep(.1)
  96. result = subprocess.run(["podman", "exec", container_id, "tar", "-xf", "archive%d.tar"%r], capture_output=True, check=True)
  97. time.sleep(.3)
  98. # Executing command in the container
  99. result = subprocess.run(["podman", "exec", container_id, *run_cmd], capture_output=True)
  100. return result.stdout + result.stderr
  101. else:
  102. raise ValueError("Invalid backend")
  103. import fcntl
  104. def is_fd_closed(fd):
  105. try:
  106. fcntl.fcntl(fd, fcntl.F_GETFD)
  107. return False
  108. except OSError:
  109. return True
  110. class DockerJob:
  111. def __init__(self, container_id, eos_string):
  112. self.eos_string = eos_string
  113. if BACKEND == "docker":
  114. cmd = f"docker exec -it {container_id} /bin/bash"
  115. print("Running", cmd)
  116. else:
  117. cmd = f"podman exec -it {container_id} /bin/bash"
  118. self.process = subprocess.Popen(cmd,
  119. shell=True,
  120. stdin=subprocess.PIPE,
  121. stdout=subprocess.PIPE,
  122. stderr=subprocess.PIPE,
  123. text=True)
  124. self.master_fd = self.process.stdout.fileno() # If you need a file descriptor for reading output
  125. @staticmethod
  126. def remove_ansi(text):
  127. ansi_escape =re.compile(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]')
  128. return ansi_escape.sub('', text)
  129. def __call__(self, cmd):
  130. # Send the command through the PTY
  131. print("GO", self.process.stdin)
  132. try:
  133. self.process.stdin.write((cmd + "\n"))
  134. self.process.stdin.flush()
  135. except:
  136. print("Process was terminated")
  137. return "Process was terminated"
  138. # Read the output until the EOS string is encountered
  139. output = []
  140. while True:
  141. ready, _, _ = select.select([self.master_fd], [], [], 2) # 2-second timeout
  142. if ready:
  143. line = os.read(self.master_fd, 128).decode()
  144. output.append(line)
  145. if self.eos_string in line:
  146. break
  147. if line == '':
  148. break
  149. else:
  150. # Timeout occurred
  151. print("Timeout - no output received in 2 seconds")
  152. break
  153. output = ''.join(output)
  154. output = self.remove_ansi(output)
  155. print("Output:", repr(output))
  156. return output
  157. def invoke_docker(env, files, run_cmd, out_bytes=False):
  158. if env.docker is None:
  159. setup_docker(env)
  160. def raise_timeout(signum, frame):
  161. raise TimeoutError
  162. signal.signal(signal.SIGALRM, raise_timeout)
  163. signal.alarm(20)
  164. try:
  165. # Function call that might take too long
  166. out = safe_run(env.docker, env.container, files, run_cmd)
  167. except TimeoutError:
  168. out = b"Timeout: function took too long to complete"
  169. signal.alarm(0)
  170. if out_bytes:
  171. return out
  172. else:
  173. return out.decode("utf-8")
  174. if I_HAVE_BLIND_FAITH_IN_LLMS_AND_AM_OKAY_WITH_THEM_BRICKING_MY_MACHINE_OR_MAKING_THEM_HALT_AND_CATCH_FIRE:
  175. class DockerJob:
  176. def __init__(self, container_id, eos_string):
  177. raise NotImplementedError("This test is not implemented in unsafe mode yet")
  178. def setup_docker(env):
  179. import random
  180. env.fake_docker_id = random.randint(0, 10000000000)
  181. os.mkdir("/tmp/fakedocker_%d"%env.fake_docker_id)
  182. def invoke_docker(env, files, run_cmd, out_bytes=False):
  183. if env.docker is None:
  184. setup_docker(env)
  185. def raise_timeout(signum, frame):
  186. raise TimeoutError
  187. signal.signal(signal.SIGALRM, raise_timeout)
  188. signal.alarm(20)
  189. try:
  190. # Function call that might take too long
  191. for file_name, file_content in files.items():
  192. with open("/tmp/fakedocker_%d/%s"%(env.fake_docker_id, file_name), "wb") as f:
  193. f.write(file_content)
  194. proc = subprocess.run(run_cmd, cwd="/tmp/fakedocker_%d"%env.fake_docker_id, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  195. except TimeoutError:
  196. if out_bytes:
  197. return b"Timeout: function took too long to complete"
  198. else:
  199. return "Timeout: function took too long to complete"
  200. signal.alarm(0)
  201. if out_bytes:
  202. return proc.stdout + proc.stderr
  203. else:
  204. stdout = proc.stdout.decode("utf-8")
  205. stderr = proc.stderr.decode("utf-8")
  206. # Replace /fakedocker_[0-9]*/ with /fakedocker/
  207. stdout = re.sub(r'/fakedocker_[0-9]*/', '/fakedocker/', stdout)
  208. stderr = re.sub(r'/fakedocker_[0-9]*/', '/fakedocker/', stderr)
  209. return stdout + stderr