Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,20 @@ Done! You have your own AI cluster now.

![Chat](docs/images/chat_interface.png)

#### Chat on Node
#### Accessing the chat interface from another non-scheduler computer

If you are only running the node service on your machine, you can visit http://localhost:3002 to access the chat page.
You can access the chat interface from any non-scheduler computer, not just those running a node server. Simply start the chat server with:

```sh
# local area network env
parallax chat
# public network env
parallax chat -s {scheduler-address}
# example
parallax chat -s 12D3KooWLX7MWuzi1Txa5LyZS4eTQ2tPaJijheH8faHggB9SxnBu
```

After launching, visit [http://localhost:3002](http://localhost:3002) in your browser to use the chat interface.

### Without frontend
#### Step 1: Launch scheduler
Expand Down
49 changes: 49 additions & 0 deletions src/parallax/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,38 @@ def join_command(args, passthrough_args: list[str] | None = None):
_execute_with_graceful_shutdown(cmd, env=env)


def chat_command(args, passthrough_args: list[str] | None = None):
"""Start the Parallax chat server (equivalent to scripts/chat.sh)."""
check_python_version()

project_root = get_project_root()
launch_script = project_root / "src" / "parallax" / "launch_chat.py"

if not launch_script.exists():
print(f"Error: Launch chat script not found at {launch_script}")
sys.exit(1)

# Build the command to run the launch_chat.py script
passthrough_args = passthrough_args or []
cmd = [sys.executable, str(launch_script)]

cmd.extend(["--scheduler-addr", args.scheduler_addr])

# Relay logic based on effective scheduler address
if args.use_relay or (
args.scheduler_addr != "auto" and not str(args.scheduler_addr).startswith("/")
):
logger.info("Using public relay servers")
cmd.extend(_get_relay_params())

# Append any passthrough args (unrecognized by this CLI) directly to the command
if passthrough_args:
cmd.extend(passthrough_args)

logger.info(f"Scheduler address: {args.scheduler_addr}")
_execute_with_graceful_shutdown(cmd)


def collect_machine_info():
"""Collect machine information."""
version = get_current_version()
Expand Down Expand Up @@ -386,6 +418,21 @@ def main():
"-u", "--skip-upload", action="store_true", help="Skip upload package info"
)

# Add 'chat' command parser
chat_parser = subparsers.add_parser(
"chat", help="Start the Parallax chat server (equivalent to scripts/chat.sh)"
)
chat_parser.add_argument(
"-s",
"--scheduler-addr",
default="auto",
type=str,
help="Scheduler address (required)",
)
chat_parser.add_argument(
"-r", "--use-relay", action="store_true", help="Use public relay servers"
)

# Accept unknown args and pass them through to the underlying python command
args, passthrough_args = parser.parse_known_args()

Expand All @@ -397,6 +444,8 @@ def main():
run_command(args, passthrough_args)
elif args.command == "join":
join_command(args, passthrough_args)
elif args.command == "chat":
chat_command(args, passthrough_args)
else:
parser.print_help()
sys.exit(1)
Expand Down
18 changes: 0 additions & 18 deletions src/parallax/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
from parallax.p2p.server import ServerState, launch_p2p_server
from parallax.server.executor import Executor
from parallax.server.http_server import launch_http_server
from parallax.server.node_chat_http_server import launch_node_chat_http_server
from parallax.server.server_args import parse_args
from parallax.utils.utils import get_current_device
from parallax_utils.ascii_anime import display_parallax_join
Expand All @@ -49,7 +48,6 @@
gradient_server = None
http_server_process = None
executor = None
node_chat_http_server_process = None
try:
args = parse_args()
set_log_level(args.log_level)
Expand Down Expand Up @@ -140,7 +138,6 @@
if args.start_layer == 0:
http_server_process = launch_http_server(args)
executor = Executor.create_from_args(args)
node_chat_http_server_process = launch_node_chat_http_server(args)

if gradient_server is not None:
gradient_server.status = ServerState.READY
Expand All @@ -166,21 +163,6 @@ def terminate_http_server_process(process):
target=terminate_http_server_process, args=(http_server_process,)
)
t.start()
if node_chat_http_server_process is not None:

def terminate_node_chat_http_server_process(process):
logger.debug("Terminating node chat HTTP server process...")
try:
process.kill()
process.join()
except Exception as e:
logger.error(f"Failed to terminate node chat HTTP server process: {e}")

t = threading.Thread(
target=terminate_node_chat_http_server_process,
args=(node_chat_http_server_process,),
)
t.start()
if gradient_server is not None:
gradient_server.shutdown()
if executor is not None:
Expand Down
17 changes: 17 additions & 0 deletions src/parallax/launch_chat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from parallax.server.node_chat_http_server import run_node_chat_http_server
from parallax.server.server_args import parse_args
from parallax_utils.logging_config import get_logger, set_log_level

logger = get_logger(__name__)

if __name__ == "__main__":
try:
args = parse_args()
set_log_level(args.log_level)
logger.debug(f"args: {args}")

run_node_chat_http_server(args)
except KeyboardInterrupt:
logger.debug("Received interrupt signal, shutting down...")
except Exception as e:
logger.exception(e)
11 changes: 2 additions & 9 deletions src/parallax/server/node_chat_http_server.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import json
import multiprocessing as mp
import time
from typing import Dict

Expand Down Expand Up @@ -321,12 +320,6 @@ def run(self):
asyncio.run(self.run_uvicorn())


def launch_node_chat_http_server(args):
"""
Launch function of node chat http server.
It creates a sub-process for the http server.
"""
def run_node_chat_http_server(args):
node_chat_http_server = NodeChatHttpServer(args)
process = mp.Process(target=node_chat_http_server.run)
process.start()
return process
node_chat_http_server.run()