Skip to content

Commit 89f64e2

Browse files
committed
gptoss image
1 parent 9981f1c commit 89f64e2

File tree

4 files changed

+86
-68
lines changed

4 files changed

+86
-68
lines changed

.github/workflows/build-publish.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,30 @@ jobs:
9696
-f Dockerfile.cuda \
9797
. \
9898
--push
99+
100+
build-cuda-gptoss:
101+
name: Build and Publish CUDA Image with GPT-OSS
102+
runs-on: [self-hosted, Linux, X64, nvidia]
103+
permissions:
104+
contents: read
105+
packages: write
106+
107+
steps:
108+
- name: Checkout HoML Repository
109+
uses: actions/checkout@v4
110+
111+
- name: Login to GitHub Container Registry
112+
uses: docker/login-action@v3
113+
with:
114+
registry: ghcr.io
115+
username: ${{ github.actor }}
116+
password: ${{ secrets.GITHUB_TOKEN }}
117+
118+
- name: Build and Push HoML Server CUDA Image
119+
working-directory: ./server
120+
run: |
121+
docker buildx build \
122+
-t ghcr.io/${{ github.repository_owner }}/homl/server:latest-cuda-gptoss \
123+
-f Dockerfile.cuda-gptoss \
124+
. \
125+
--push

cli/homl_cli/main.py

Lines changed: 31 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -76,64 +76,9 @@ def check_and_install_docker():
7676
if docker_exists and compose_exists:
7777
return True
7878

79-
if docker_exists and not compose_exists:
80-
click.secho("🔥 Docker Compose (plugin) not found.", fg="yellow")
81-
# Try to install docker compose via local package manager
82-
distro = None
83-
distro_like = None
84-
try:
85-
with open("/etc/os-release") as f:
86-
for line in f:
87-
if line.startswith("ID="):
88-
distro = line.strip().split("=")[1].strip('"')
89-
elif line.startswith("ID_LIKE="):
90-
distro_like = line.strip().split("=")[1].strip('"')
91-
except Exception:
92-
pass
93-
installed = False
94-
if distro in ["ubuntu", "debian"]:
95-
click.echo("Attempting to install docker-compose-plugin via apt...")
96-
try:
97-
subprocess.run(["sudo", "apt-get", "update"], check=True)
98-
subprocess.run(["sudo", "apt-get", "install", "-y", "docker-compose-plugin"], check=True)
99-
# Re-check if compose is now available
100-
result = subprocess.run(["docker", "compose", "version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
101-
if result.returncode == 0:
102-
click.secho("✅ Docker Compose plugin installed successfully.", fg="green")
103-
return True
104-
else:
105-
click.secho("❌ Docker Compose plugin installation failed.", fg="red")
106-
except Exception as e:
107-
click.secho(f"❌ Failed to install docker-compose-plugin: {e}", fg="red")
108-
elif distro in ["fedora", "centos", "rhel", "amzn"] or (distro_like and "fedora" in distro_like):
109-
click.echo("Attempting to install docker-compose-plugin via dnf...")
110-
try:
111-
subprocess.run(["sudo", "dnf", "install", "-y", "docker-compose-plugin"], check=True)
112-
result = subprocess.run(["docker", "compose", "version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
113-
if result.returncode == 0:
114-
click.secho("✅ Docker Compose plugin installed successfully.", fg="green")
115-
return True
116-
else:
117-
click.secho("❌ Docker Compose plugin installation failed.", fg="red")
118-
except Exception as e:
119-
click.secho(f"❌ Failed to install docker-compose-plugin: {e}", fg="red")
120-
elif distro in ["arch", "manjaro"]:
121-
click.echo("Attempting to install docker-compose via pacman...")
122-
try:
123-
subprocess.run(["sudo", "pacman", "-Sy", "docker-compose"], check=True)
124-
result = subprocess.run(["docker", "compose", "version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
125-
if result.returncode == 0:
126-
click.secho("✅ Docker Compose installed successfully.", fg="green")
127-
return True
128-
else:
129-
click.secho("❌ Docker Compose installation failed.", fg="red")
130-
except Exception as e:
131-
click.secho(f"❌ Failed to install docker-compose: {e}", fg="red")
132-
# If not installed, fallback to official script
133-
click.secho("Could not install Docker Compose plugin via package manager.", fg="yellow")
134-
135-
click.secho("🔥 Docker or Docker Compose not found.", fg="yellow")
136-
if click.confirm("May I attempt to install them using the official script? (Requires sudo)"):
79+
# Offer to install via official Docker script
80+
click.secho("🔥 Docker or Docker Compose not found.", fg="red")
81+
if click.confirm("May I attempt to install Docker using the official script? (Requires sudo)"):
13782
try:
13883
click.echo("Downloading Docker installation script...")
13984
script_path = "/tmp/get-docker.sh"
@@ -148,21 +93,29 @@ def check_and_install_docker():
14893
click.echo("Adding current user to the 'docker' group...")
14994
subprocess.run(["sudo", "usermod", "-aG", "docker", os.getlogin()], check=True)
15095
click.secho("Please log out and log back in for the group changes to take effect.", fg="yellow")
151-
return True
96+
# Re-check for docker and compose
97+
docker_exists = shutil.which("docker") is not None
98+
try:
99+
result = subprocess.run(["docker", "compose", "version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
100+
compose_exists = result.returncode == 0
101+
except Exception:
102+
compose_exists = False
103+
if docker_exists and compose_exists:
104+
return True
152105
except (subprocess.CalledProcessError, FileNotFoundError) as e:
153106
click.secho(f"❌ Docker installation failed: {e}", fg="red")
154-
return False
155-
else:
156-
click.echo("Installation cancelled.")
157-
return False
158107

159-
def get_platform_config(accelerator: str) -> Dict[str, Any]:
108+
click.secho("Please install both Docker and Docker Compose (plugin) manually using your package manager or official instructions, then try again.", fg="yellow")
109+
click.secho("See: https://docs.docker.com/engine/install/ and https://docs.docker.com/compose/install/", fg="yellow")
110+
return False
111+
112+
def get_platform_config(accelerator: str, gptoss: bool) -> Dict[str, Any]:
160113
"""Returns the docker image and other config for a given platform."""
161114
# In the future, these images would be hosted on a public registry.
162115
# For now, they are conceptual names.
163116
if accelerator == "cuda":
164117
return {
165-
"image": "ghcr.io/wsmlby/homl/server:latest-cuda",
118+
"image": "ghcr.io/wsmlby/homl/server:latest-cuda" if not gptoss else "ghcr.io/wsmlby/homl/server:latest-cuda-gptoss",
166119
"deploy_resources": """
167120
resources:
168121
reservations:
@@ -252,7 +205,8 @@ def detect_platform() -> Dict[str, str]:
252205
@server.command()
253206
@click.option('--insecure-socket', is_flag=True, help="Use a world-writable socket (less secure).")
254207
@click.option('--upgrade', is_flag=True, help="Force reinstallation even if the server is already running.")
255-
def install(insecure_socket: bool, upgrade: bool):
208+
@click.option('--gptoss', is_flag=True, help="Use the GPTOSS image instead of the default.")
209+
def install(insecure_socket: bool, upgrade: bool, gptoss: bool):
256210
"""Installs and starts the HoML server using Docker Compose."""
257211
click.echo("🚀 Starting HoML server installation...")
258212

@@ -266,7 +220,7 @@ def install(insecure_socket: bool, upgrade: bool):
266220
click.echo(f"Detected Platform: {platform.get('accelerator', 'cpu')}")
267221

268222
accelerator = platform.get("accelerator", "cpu")
269-
platform_config = get_platform_config(accelerator)
223+
platform_config = get_platform_config(accelerator, gptoss)
270224

271225
# 3. Check for platform-specific dependencies
272226
if accelerator == "cuda":
@@ -445,6 +399,16 @@ def stop(model_name):
445399
def pull(model_name):
446400
"""Pulls a model from a registry."""
447401
stub = get_client_stub()
402+
if "gptoss" in model_name.lower():
403+
# check if running container homl-homl-server-1 is the GPTOSS image, if not, ask the user to use the gptoss flag and reinstall
404+
subprocess.run(["docker", "ps", "-f", "name=homl-homl-server-1", "--format", "{{.Image}}"], check=True, capture_output=True)
405+
image_name = subprocess.run(["docker", "ps", "-f", "name=homl-homl-server-1", "--format", "{{.Image}}"], check=True, capture_output=True, text=True).stdout.strip()
406+
if "gptoss" not in image_name.lower():
407+
click.secho("You are trying to pull a GPTOSS model, but the server is not running with the GPTOSS image.", fg="red")
408+
click.secho("Please use the --gptoss flag when to reinstall the server with 'homl server install --gptoss'.", fg="yellow")
409+
click.secho("GPTOSS support is experimental, if you encounter issues, you can revert by reinstall using 'homl server install'.", fg="yellow")
410+
return
411+
448412
# get the Hugging Face token from config
449413
hf_token = config.get_config_value("hugging_face_token", "")
450414
if stub:

server/Dockerfile.cuda

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# It layers our application code on top of the official vLLM OpenAI image.
33

44
# Base image for CUDA builds
5-
FROM vllm/vllm-openai:gptoss
5+
FROM vllm/vllm-openai:latest
66

77
# Set the working directory
88
WORKDIR /app

server/Dockerfile.cuda-gptoss

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# This Dockerfile builds the final HoML Server image for NVIDIA CUDA.
2+
# It layers our application code on top of the official vLLM OpenAI image.
3+
4+
# Base image for CUDA builds
5+
FROM vllm/vllm-openai:gptoss
6+
7+
# Set the working directory
8+
WORKDIR /app
9+
10+
11+
# Copy requirements.txt and install dependencies
12+
COPY requirements.txt ./
13+
RUN pip install -r requirements.txt
14+
15+
# Optionally install from pyproject.toml if needed
16+
# COPY pyproject.toml .
17+
# RUN pip install .
18+
ENV ACCELERATOR=CUDA
19+
# Copy our application source code
20+
COPY ./homl_server ./homl_server
21+
WORKDIR /app/homl_server
22+
# The base image exposes port 8000, so we don't need to do it again.
23+
# EXPOSE 8000
24+
25+
# We overwrite the base image's entrypoint to run our custom Ray Serve
26+
# application, which provides dynamic model management.
27+
ENTRYPOINT [ "python3", "-u", "main.py"]

0 commit comments

Comments
 (0)