Skip to content

Commit 2fb3725

Browse files
Release 6.1.2 (#301)
1 parent 0079a42 commit 2fb3725

File tree

8 files changed

+322
-42
lines changed

8 files changed

+322
-42
lines changed

docs/lemonade/server_integration.md

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ The first part of this guide contains instructions that are common for both inte
1010

1111
## General Instructions
1212

13+
### Identifying Existing Installation
14+
15+
To identify if Lemonade Server is installed on a system, you can use the `lemonade-server` CLI command, which is added to path when using our installer. This is a reliable method to:
16+
- Verify if the server is installed.
17+
- Check which version is currently available is running the command below.
18+
19+
```
20+
lemonade-server --version
21+
```
22+
23+
>Note: The `lemonade-server` CLI command is added to PATH when using the Windows Installer (Lemonade_Server_Installer.exe). For Linux users or Python development environments, the command `lemonade-server-dev` is available when installing via pip.
24+
25+
### Checking Server Status
26+
27+
To identify whether or not the server is running anywhere on the system you may use the `status` command of `lemonade-server`.
28+
29+
```
30+
lemonade-server status
31+
```
32+
33+
This command will return either `Server is not running` or `Server is running on port <PORT>`.
1334

1435
### Identifying Compatible Devices
1536

@@ -54,8 +75,18 @@ Please note that the Server Installer is only available on Windows. Apps that in
5475
Some apps might prefer to be responsible for installing and managing Lemonade Server on behalf of the user. This part of the guide includes steps for installing and running Lemonade Server so that your users don't have to install Lemonade Server separately.
5576

5677
Definitions:
57-
- "Silent installation" refers to an automatic command for installing Lemonade Server without running any GUI or prompting the user for any questions. It does assume that the end-user fully accepts the license terms, so be sure that your own application makes this clear to the user.
5878
- Command line usage allows the server process to be launched programmatically, so that your application can manage starting and stopping the server process on your user's behalf.
79+
- "Silent installation" refers to an automatic command for installing Lemonade Server without running any GUI or prompting the user for any questions. It does assume that the end-user fully accepts the license terms, so be sure that your own application makes this clear to the user.
80+
81+
### Command Line Invocation
82+
83+
This command line invocation starts the Lemonade Server process so that your application can connect to it via REST API endpoints. To start the server, simply run the command below.
84+
85+
```bash
86+
lemonade-server serve
87+
```
88+
89+
You can also run the server as a background process using a subprocess or any preferred method.
5990

6091
### Silent Installation
6192

@@ -97,37 +128,3 @@ The available modes are the following:
97128
* `Qwen-1.5-7B-Chat-Hybrid`
98129
* `DeepSeek-R1-Distill-Llama-8B-Hybrid`
99130
* `DeepSeek-R1-Distill-Qwen-7B-Hybrid`
100-
101-
### Command Line Invocation
102-
103-
Command line invocation starts the Lemonade Server process so that your application can connect to it via REST API endpoints.
104-
105-
#### Foreground Process
106-
107-
These steps will open the Lemonade Server in a terminal window that is visible to users. The user can exit the server by closing the window.
108-
109-
In a `cmd.exe` terminal:
110-
111-
```bash
112-
conda run --no-capture-output -p INSTALL_DIR\lemonade_server\lemon_env lemonade serve
113-
```
114-
115-
Where `INSTALL_DIR` is the installation path of `lemonade_server`.
116-
117-
For example, if you used the default installation directory and your username is USERNAME:
118-
119-
```bash
120-
C:\Windows\System32\cmd.exe /C conda run --no-capture-output -p C:\Users\USERNAME\AppData\Local\lemonade_server\lemon_env lemonade serve
121-
```
122-
123-
#### Background Process
124-
125-
This command will open the Lemonade Server without opening a window. Your application needs to manage terminating the process and any child processes it creates.
126-
127-
In a powershell terminal:
128-
129-
```powershell
130-
$serverProcess = Start-Process -FilePath "C:\Windows\System32\cmd.exe" -ArgumentList "/C conda run --no-capture-output -p INSTALL_DIR\lemonade_server\lemon_env lemonade serve" -RedirectStandardOutput lemonade_out.txt -RedirectStandardError lemonade_err.txt -PassThru -NoNewWindow
131-
```
132-
133-
Where `INSTALL_DIR` is the installation path of `lemonade_server`.

installer/Installer.nsi

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,11 @@ SectionIn RO ; Read only, always installed
9595

9696
# Pack turnkeyml repo into the installer
9797
# Exclude hidden files (like .git, .gitignore) and the installation folder itself
98-
File /r /x nsis.exe /x installer /x .* /x *.pyc /x docs /x examples /x utilities ..\*.* run_server.bat
98+
File /r /x nsis.exe /x installer /x .* /x *.pyc /x docs /x examples /x utilities ..\*.* lemonade_server.bat
99+
100+
# Create bin directory and move lemonade_server.bat there
101+
CreateDirectory "$INSTDIR\bin"
102+
Rename "$INSTDIR\lemonade_server.bat" "$INSTDIR\bin\lemonade_server.bat"
99103

100104
DetailPrint "- Packaged repo"
101105

@@ -196,7 +200,18 @@ SectionIn RO ; Read only, always installed
196200

197201
DetailPrint "*** INSTALLATION COMPLETED ***"
198202
# Create a shortcut inside $INSTDIR
199-
CreateShortcut "$INSTDIR\lemonade-server.lnk" "$SYSDIR\cmd.exe" "/C conda run --no-capture-output -p $INSTDIR\$LEMONADE_CONDA_ENV lemonade serve" "$INSTDIR\img\favicon.ico"
203+
CreateShortcut "$INSTDIR\lemonade-server.lnk" "$INSTDIR\bin\lemonade_server.bat" "serve --keep-alive" "$INSTDIR\img\favicon.ico"
204+
205+
; Add bin folder to system PATH
206+
DetailPrint "- Adding bin directory to system PATH..."
207+
208+
; Get the current PATH value from the registry
209+
ReadRegStr $0 HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" "PATH"
210+
211+
; Add bin folder (containing 'lemonade-server') to path while avoiding duplicate entries
212+
ExecWait 'setx PATH "$INSTDIR\bin;$0" -m'
213+
214+
DetailPrint "- Successfully updated system PATH"
200215

201216
Goto end
202217

@@ -298,7 +313,7 @@ SubSectionEnd
298313

299314
Section "-Add Desktop Shortcut" ShortcutSec
300315
; Create a desktop shortcut that passes the conda environment name as a parameter
301-
CreateShortcut "$DESKTOP\lemonade-server.lnk" "$INSTDIR\run_server.bat" "$LEMONADE_CONDA_ENV" "$INSTDIR\img\favicon.ico"
316+
CreateShortcut "$DESKTOP\lemonade-server.lnk" "$INSTDIR\bin\lemonade_server.bat" "serve --keep-alive" "$INSTDIR\img\favicon.ico"
302317

303318
SectionEnd
304319

@@ -551,5 +566,7 @@ Function .onInit
551566
${EndIf}
552567
${EndIf}
553568

569+
; Call onSelChange to ensure initial model selection state is correct
570+
Call .onSelChange
554571

555572
FunctionEnd

installer/lemonade_server.bat

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@echo off
2+
setlocal enabledelayedexpansion
3+
set CONDA_ENV=lemon_env
4+
5+
REM --keep-alive is only used by the bash script to make sure that, if the server fails to open, we don't close the terminal right away.
6+
REM Check for --keep-alive argument and remove it from arguments passed to CLI
7+
set KEEP_ALIVE=0
8+
set ARGS=
9+
for %%a in (%*) do (
10+
if /I "%%a"=="--keep-alive" (
11+
set KEEP_ALIVE=1
12+
) else (
13+
set ARGS=!ARGS! %%a
14+
)
15+
)
16+
17+
REM Change to parent directory where conda env and bin folders are located
18+
pushd "%~dp0.."
19+
20+
REM Run the Python CLI script through conda, passing filtered arguments
21+
call conda run --no-capture-output -p "%CD%\%CONDA_ENV%" lemonade-server-dev !ARGS!
22+
popd
23+
24+
REM Error handling: Show message and pause if --keep-alive was specified
25+
if %ERRORLEVEL% neq 0 (
26+
if %KEEP_ALIVE%==1 (
27+
echo.
28+
echo An error occurred while running Lemonade Server.
29+
echo Please check the error message above.
30+
echo.
31+
pause
32+
)
33+
)

setup.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
version=version,
1010
description="TurnkeyML Tools and Models",
1111
author_email="[email protected]",
12-
package_dir={"": "src", "turnkeyml_models": "models"},
12+
package_dir={
13+
"": "src",
14+
"turnkeyml_models": "models",
15+
},
1316
packages=[
1417
"turnkeyml",
1518
"turnkeyml.tools",
@@ -30,6 +33,7 @@
3033
"turnkeyml_models.torchvision",
3134
"turnkeyml_models.transformers",
3235
"lemonade_install",
36+
"lemonade_server",
3337
],
3438
install_requires=[
3539
"invoke>=2.0.0",
@@ -109,6 +113,7 @@
109113
"turnkey-llm=lemonade:lemonadecli",
110114
"lemonade=lemonade:lemonadecli",
111115
"lemonade-install=lemonade_install:installcli",
116+
"lemonade-server-dev=lemonade_server.cli:main",
112117
]
113118
},
114119
python_requires=">=3.8, <3.12",

src/lemonade/tools/serve.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,8 @@ def parser(add_help: bool = True) -> argparse.ArgumentParser:
242242

243243
def run(
244244
self,
245-
cache_dir: str,
246-
checkpoint: str,
245+
cache_dir: str = DEFAULT_CACHE_DIR,
246+
checkpoint: str = None,
247247
max_new_tokens: int = DEFAULT_MAX_NEW_TOKENS,
248248
port: int = DEFAULT_PORT,
249249
log_level: str = DEFAULT_LOG_LEVEL,

src/lemonade_server/cli.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import argparse
2+
import sys
3+
import os
4+
import psutil
5+
6+
7+
def serve(args):
8+
"""
9+
Execute the serve command
10+
"""
11+
12+
# Check if Lemonade Server is already running
13+
running_on_port = get_server_port()
14+
if running_on_port is not None:
15+
print(
16+
(
17+
f"Lemonade Server is already running on port {running_on_port}\n"
18+
"Please stop the existing server before starting a new instance."
19+
),
20+
)
21+
return
22+
23+
# Otherwise, start the server
24+
print("Starting Lemonade Server...")
25+
from lemonade.tools.serve import Server
26+
27+
server = Server()
28+
server.run()
29+
30+
31+
def version():
32+
"""
33+
Print the version number
34+
"""
35+
from turnkeyml import __version__ as version_number
36+
37+
print(f"Lemonade Server version is {version_number}")
38+
39+
40+
def status():
41+
"""
42+
Print the status of the server
43+
"""
44+
port = get_server_port()
45+
if port is None:
46+
print("Server is not running")
47+
else:
48+
print(f"Server is running on port {port}")
49+
50+
51+
def is_lemonade_server(pid):
52+
"""
53+
Check wether or not a given PID corresponds to a Lemonade server
54+
"""
55+
try:
56+
process = psutil.Process(pid)
57+
while True:
58+
if process.name() in [ # Windows
59+
"lemonade-server-dev.exe",
60+
"lemonade-server.exe",
61+
"lemonade.exe",
62+
] or process.name() in [ # Linux
63+
"lemonade-server-dev",
64+
"lemonade-server",
65+
"lemonade",
66+
]:
67+
return True
68+
if not process.parent():
69+
return False
70+
process = process.parent()
71+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
72+
return False
73+
return False
74+
75+
76+
def get_server_port() -> int | None:
77+
"""
78+
Get the port that Lemonade Server is running on
79+
"""
80+
# Go over all python processes that have a port open
81+
for process in psutil.process_iter(["pid", "name"]):
82+
try:
83+
connections = process.net_connections()
84+
for conn in connections:
85+
if conn.status == "LISTEN":
86+
if is_lemonade_server(process.info["pid"]):
87+
return conn.laddr.port
88+
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
89+
continue
90+
91+
return None
92+
93+
94+
def main():
95+
parser = argparse.ArgumentParser(
96+
description="Serve LLMs on CPU, GPU, and NPU.",
97+
usage=argparse.SUPPRESS,
98+
)
99+
100+
# Add version flag
101+
parser.add_argument(
102+
"-v", "--version", action="store_true", help="Show version number"
103+
)
104+
105+
# Create subparsers for commands
106+
subparsers = parser.add_subparsers(
107+
title="Available Commands", dest="command", metavar=""
108+
)
109+
110+
# Serve commands
111+
serve_parser = subparsers.add_parser("serve", help="Start server")
112+
status_parser = subparsers.add_parser("status", help="Check if server is running")
113+
114+
args = parser.parse_args()
115+
116+
if args.version:
117+
version()
118+
elif args.command == "serve":
119+
serve(args)
120+
elif args.command == "status":
121+
status()
122+
elif args.command == "help" or not args.command:
123+
parser.print_help()
124+
125+
126+
if __name__ == "__main__":
127+
main()

src/turnkeyml/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "6.1.1"
1+
__version__ = "6.1.2"

0 commit comments

Comments
 (0)