From 3c3c81ea8ef226511b20c96c3a760c221d5e9e4d Mon Sep 17 00:00:00 2001 From: sibianl Date: Tue, 28 Oct 2025 16:38:04 +0800 Subject: [PATCH 1/2] feat(chat): Separate the chat page server from the node --- README.md | 15 +++++- src/parallax/cli.py | 49 ++++++++++++++++++++ src/parallax/launch.py | 18 ------- src/parallax/launch_chat.py | 17 +++++++ src/parallax/server/node_chat_http_server.py | 5 ++ 5 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 src/parallax/launch_chat.py diff --git a/README.md b/README.md index 934f25dc..85affab2 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/parallax/cli.py b/src/parallax/cli.py index 8d653523..ffdb4ddc 100644 --- a/src/parallax/cli.py +++ b/src/parallax/cli.py @@ -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() @@ -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() @@ -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) diff --git a/src/parallax/launch.py b/src/parallax/launch.py index 93e18872..e79dcd50 100644 --- a/src/parallax/launch.py +++ b/src/parallax/launch.py @@ -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 @@ -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) @@ -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 @@ -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: diff --git a/src/parallax/launch_chat.py b/src/parallax/launch_chat.py new file mode 100644 index 00000000..8cd4d884 --- /dev/null +++ b/src/parallax/launch_chat.py @@ -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) diff --git a/src/parallax/server/node_chat_http_server.py b/src/parallax/server/node_chat_http_server.py index f7c44a5d..326dcede 100644 --- a/src/parallax/server/node_chat_http_server.py +++ b/src/parallax/server/node_chat_http_server.py @@ -330,3 +330,8 @@ def launch_node_chat_http_server(args): process = mp.Process(target=node_chat_http_server.run) process.start() return process + + +def run_node_chat_http_server(args): + node_chat_http_server = NodeChatHttpServer(args) + node_chat_http_server.run() From ed18c70ffd4227eef7fc10847458a510c08b572e Mon Sep 17 00:00:00 2001 From: sibianl Date: Tue, 28 Oct 2025 18:03:09 +0800 Subject: [PATCH 2/2] fix --- src/parallax/server/node_chat_http_server.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/parallax/server/node_chat_http_server.py b/src/parallax/server/node_chat_http_server.py index 326dcede..43d01928 100644 --- a/src/parallax/server/node_chat_http_server.py +++ b/src/parallax/server/node_chat_http_server.py @@ -1,6 +1,5 @@ import asyncio import json -import multiprocessing as mp import time from typing import Dict @@ -321,17 +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. - """ - node_chat_http_server = NodeChatHttpServer(args) - process = mp.Process(target=node_chat_http_server.run) - process.start() - return process - - def run_node_chat_http_server(args): node_chat_http_server = NodeChatHttpServer(args) node_chat_http_server.run()