Skip to content

Commit 0e61654

Browse files
committed
LocalLab Server Package v0.1.0 Released and Updated Docs
1 parent a8bd2a3 commit 0e61654

File tree

7 files changed

+148
-102
lines changed

7 files changed

+148
-102
lines changed

.github/workflows/publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish Python Package
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
build-and-publish:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v3
14+
15+
- name: Set up Python
16+
uses: actions/setup-python@v4
17+
with:
18+
python-version: "3.x"
19+
20+
- name: Install build dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install build twine
24+
25+
- name: Build package
26+
run: python -m build --sdist --wheel
27+
28+
- name: Publish package to PyPI
29+
uses: pypa/[email protected]
30+
with:
31+
user: __token__
32+
password: ${{ secrets.PYPI_API_TOKEN }}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Developer Utkarsh
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
[![Build Status](https://github.com/Developer-Utkarsh/LocalLab/workflows/CI/badge.svg)](https://github.com/Developer-Utkarsh/LocalLab/actions)
44
[![Coverage Status](https://coveralls.io/repos/github/Developer-Utkarsh/LocalLab/badge.svg?branch=main)](https://coveralls.io/github/Developer-Utkarsh/LocalLab?branch=main)
5+
[![LocalLab Version](https://img.shields.io/pypi/v/locallab.svg)](https://pypi.org/project/locallab/)
56
[![Python Version](https://img.shields.io/pypi/pyversions/locallab.svg)](https://pypi.org/project/locallab/)
67
[![License](https://img.shields.io/github/license/Developer-Utkarsh/LocalLab.svg)](https://github.com/Developer-Utkarsh/LocalLab/blob/main/LICENSE)
78

@@ -17,22 +18,24 @@ LocalLab is a powerful, lightweight AI inference server designed to deliver cutt
1718
## System Requirements
1819

1920
### Minimum Requirements
20-
| Component | Local Deployment | Google Colab |
21-
|-----------|-----------------|--------------|
22-
| RAM | 4GB | Free tier (12GB) |
23-
| CPU | 2 cores | 2 cores |
24-
| Python | 3.8+ | 3.8+ |
25-
| Storage | 2GB free | - |
26-
| GPU | Optional | Available in free tier |
21+
22+
| Component | Local Deployment | Google Colab |
23+
| --------- | ---------------- | ---------------------- |
24+
| RAM | 4GB | Free tier (12GB) |
25+
| CPU | 2 cores | 2 cores |
26+
| Python | 3.8+ | 3.8+ |
27+
| Storage | 2GB free | - |
28+
| GPU | Optional | Available in free tier |
2729

2830
### Recommended Requirements
29-
| Component | Local Deployment | Google Colab |
30-
|-----------|-----------------|--------------|
31-
| RAM | 8GB+ | Pro tier (24GB) |
32-
| CPU | 4+ cores | Pro tier (4 cores) |
33-
| Python | 3.9+ | 3.9+ |
34-
| Storage | 5GB+ free | - |
35-
| GPU | CUDA-compatible | Pro tier GPU |
31+
32+
| Component | Local Deployment | Google Colab |
33+
| --------- | ---------------- | ------------------ |
34+
| RAM | 8GB+ | Pro tier (24GB) |
35+
| CPU | 4+ cores | Pro tier (4 cores) |
36+
| Python | 3.9+ | 3.9+ |
37+
| Storage | 5GB+ free | - |
38+
| GPU | CUDA-compatible | Pro tier GPU |
3639

3740
## Key Features
3841

@@ -48,7 +51,7 @@ Below is an illustration of LocalLab's architecture:
4851

4952
```mermaid
5053
graph TD;
51-
A[User] --> B[LocalLab Client (Python/Node.js)];
54+
A[User] --> B[LocalLab Client (Python and Node.js)];
5255
B --> C[LocalLab Server];
5356
C --> D[Model Manager];
5457
D --> E[Hugging Face Models];
@@ -57,6 +60,7 @@ graph TD;
5760
```
5861

5962
### Model Loading & Optimization Flow
63+
6064
```mermaid
6165
graph TD;
6266
A[Load Model Request] --> B{Check Resources};
@@ -70,6 +74,7 @@ graph TD;
7074
```
7175

7276
### Resource Management Flow
77+
7378
```mermaid
7479
graph TD;
7580
A[Client Request] --> B[Resource Monitor];
@@ -98,35 +103,38 @@ sequenceDiagram
98103

99104
## Documentation
100105

101-
📚 [Read the full documentation](./docs/README.md)
106+
📚 [Read the full documentation](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/README.md)
102107

103-
- [Getting Started Guide](./docs/guides/getting-started.md)
104-
- [Python Client](./docs/clients/python/README.md)
105-
- [Node.js Client](./docs/clients/nodejs/README.md)
106-
- [Client Comparison](./docs/clients/comparison.md)
107-
- [Google Colab Guide](./docs/colab/README.md)
108-
- [API Reference](./docs/guides/api.md)
108+
- [Getting Started Guide](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/guides/getting-started.md)
109+
- [Python Client](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/clients/python/README.md)
110+
- [Node.js Client](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/clients/nodejs/README.md)
111+
- [Client Comparison](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/clients/comparison.md)
112+
- [Google Colab Guide](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/colab/README.md)
113+
- [API Reference](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/guides/api.md)
109114

110115
## Usage Guides
111116

112-
- Detailed setup and usage instructions can be found in our [Documentation](./docs/README.md).
113-
- For Python-specific details, check out our [Python Client Guide](./docs/clients/python.md).
114-
- For Node.js-specific details, refer to our [Node.js Client Guide](./docs/clients/nodejs.md).
117+
- Detailed setup and usage instructions can be found in our [Documentation](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/README.md).
118+
- For Python-specific details, check out our [Python Client Guide](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/clients/python.md).
119+
- For Node.js-specific details, refer to our [Node.js Client Guide](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/clients/nodejs.md).
115120

116121
## Get Started
117122

118123
1. **Installation:**
124+
119125
```bash
120126
pip install locallab
121127
```
122128

123129
2. **Starting the Server Locally:**
130+
124131
```python
125132
from locallab import start_server
126133
start_server()
127134
```
128135

129136
3. **Starting the Server on Google Colab:**
137+
130138
```python
131139
!pip install locallab
132140
import os
@@ -145,7 +153,7 @@ sequenceDiagram
145153

146154
- Report issues on our [GitHub Issues](https://github.com/Developer-Utkarsh/LocalLab/issues).
147155
- Participate in discussions on our [Community Forum](https://github.com/Developer-Utkarsh/LocalLab/discussions).
148-
- Learn how to contribute by reading our [Contributing Guidelines](./docs/guides/contributing.md).
156+
- Learn how to contribute by reading our [Contributing Guidelines](https://github.com/Developer-Utkarsh/LocalLab/blob/main/docs/guides/contributing.md).
149157

150158
---
151159

docs/colab/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Quick Start
44

55
The fastest way to get started is to use our [Interactive Colab Guide](./locallab_colab_guide.ipynb). This notebook provides:
6+
67
- Step-by-step setup
78
- Interactive configuration
89
- Usage examples
@@ -36,3 +37,13 @@ The fastest way to get started is to use our [Interactive Colab Guide](./localla
3637
- Check our [Troubleshooting Guide](./troubleshooting.md)
3738
- Visit our [FAQ](./faq.md)
3839
- Open an [Issue](https://github.com/Developer-Utkarsh/LocalLab/issues)
40+
41+
```mermaid
42+
graph TD;
43+
A[User] --> B[LocalLab Client (Python and Node.js)];
44+
B --> C[LocalLab Server];
45+
C --> D[Model Manager];
46+
D --> E[Hugging Face Models];
47+
C --> F[Optimizations];
48+
C --> G[Resource Monitoring];
49+
```

locallab/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
LocalLab - A lightweight AI inference server
33
"""
44

5-
__version__ = "0.0"
5+
__version__ = "0.1.0"
66

77
from typing import Dict, Any, Optional
88

locallab/main.py

Lines changed: 48 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
import requests
2323
import multiprocessing
2424

25+
# New: Define a LogQueueWriter to redirect writes to a multiprocessing.Queue
26+
class LogQueueWriter:
27+
def __init__(self, queue):
28+
self.queue = queue
29+
def write(self, msg):
30+
if msg.strip() != "":
31+
self.queue.put(msg)
32+
def flush(self):
33+
pass
34+
2535
from . import __version__ # Import version from package
2636
from .model_manager import ModelManager
2737
from .config import (
@@ -669,13 +679,20 @@ def setup_ngrok(port: int = 8000, max_retries: int = 3) -> Optional[str]:
669679
logger.error("Failed to establish ngrok tunnel after all retries")
670680
raise
671681

672-
def run_server_proc():
673-
try:
674-
# Redirect stdout and stderr to 'server.log' to capture all printed output
675-
log_file = "server.log"
676-
sys.stdout = open(log_file, "a", buffering=1)
677-
sys.stderr = open(log_file, "a", buffering=1)
682+
# Modify run_server_proc to accept a log_queue and redirect stdout/stderr
678683

684+
def run_server_proc(log_queue):
685+
try:
686+
# Redirect stdout and stderr to the log queue
687+
log_writer = LogQueueWriter(log_queue)
688+
sys.stdout = log_writer
689+
sys.stderr = log_writer
690+
691+
# Attach a logging handler to send log messages to the queue
692+
handler = logging.StreamHandler(log_writer)
693+
handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
694+
logger.addHandler(handler)
695+
679696
if "COLAB_GPU" in os.environ:
680697
import nest_asyncio
681698
nest_asyncio.apply()
@@ -691,15 +708,21 @@ def run_server_proc():
691708
logger.error(f"Server startup failed: {str(e)}")
692709
raise
693710

694-
def start_server(use_ngrok: bool = False):
711+
# Modify start_server to accept a log_queue parameter and pass it to the child process
712+
713+
def start_server(use_ngrok: bool = False, log_queue=None):
695714
import time
696715
import requests
697-
716+
717+
# If no log_queue provided, create one (though normally parent supplies it)
718+
if log_queue is None:
719+
log_queue = multiprocessing.Queue()
720+
698721
# Start the server in a separate process using spawn context with module-level run_server_proc
699722
ctx = multiprocessing.get_context("spawn")
700-
p = ctx.Process(target=run_server_proc)
723+
p = ctx.Process(target=run_server_proc, args=(log_queue,))
701724
p.start()
702-
725+
703726
# Wait until the /health endpoint returns 200 or timeout
704727
timeout = 30
705728
start_time_loop = time.time()
@@ -714,16 +737,16 @@ def start_server(use_ngrok: bool = False):
714737
except Exception:
715738
pass
716739
time.sleep(1)
717-
740+
718741
if not server_ready:
719742
raise Exception("Server did not become healthy in time.")
720-
743+
721744
if use_ngrok:
722745
public_url = setup_ngrok(port=8000)
723746
ngrok_section = f"\n{Fore.CYAN}┌────────────────────────── Ngrok Tunnel Details ─────────────────────────────┐{Style.RESET_ALL}\n\n│ 🚀 Ngrok Public URL: {Fore.GREEN}{public_url}{Style.RESET_ALL}\n\n{Fore.CYAN}└──────────────────────────────────────────────────────────────────────────────┘{Style.RESET_ALL}\n"
724747
logger.info(ngrok_section)
725748
print(ngrok_section)
726-
749+
727750
# Wait indefinitely until a KeyboardInterrupt is received
728751
try:
729752
while True:
@@ -733,68 +756,17 @@ def start_server(use_ngrok: bool = False):
733756
p.terminate()
734757
p.join()
735758

736-
@app.middleware("http")
737-
async def log_requests(request: Request, call_next):
738-
"""Log API requests with detailed information"""
739-
start_time = time.time()
740-
response = await call_next(request)
741-
process_time = (time.time() - start_time) * 1000
742-
743-
# Get current resource usage
744-
cpu_percent = psutil.cpu_percent()
745-
memory = psutil.virtual_memory()
746-
gpu_mem = torch.cuda.memory_allocated(0) / 1024**2 if torch.cuda.is_available() else 0
747-
748-
# Create status color based on response code
749-
status_color = Fore.GREEN if response.status_code < 300 else Fore.RED if response.status_code >= 400 else Fore.YELLOW
750-
751-
# Get endpoint description and other details
752-
endpoint_desc = {
753-
"/generate": "Text Generation",
754-
"/chat": "Chat Completion",
755-
"/generate/batch": "Batch Generation",
756-
"/models/current": "Current Model Info",
757-
"/models/available": "Available Models",
758-
"/system/info": "System Status"
759-
}.get(request.url.path, "API Request")
760-
761-
# Format request log with additional active model info
762-
log_message = f"""
763-
{Fore.CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Style.RESET_ALL}
764-
{Fore.CYAN}{endpoint_desc}{Style.RESET_ALL}
765-
{Fore.CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{Style.RESET_ALL}
766-
767-
• Time: {Fore.GREEN}{time.strftime('%H:%M:%S')}{Style.RESET_ALL}
768-
• Method: {Fore.MAGENTA}{request.method}{Style.RESET_ALL}
769-
• Path: {Fore.YELLOW}{request.url.path}{Style.RESET_ALL}
770-
• Status: {status_color}{response.status_code}{Style.RESET_ALL}
771-
• Duration: {Fore.GREEN}{process_time:.2f}ms{Style.RESET_ALL}
772-
773-
{Fore.YELLOW}Resource Usage:{Style.RESET_ALL}
774-
• CPU: {Fore.YELLOW}{cpu_percent}%{Style.RESET_ALL}
775-
• RAM: {Fore.YELLOW}{memory.percent}%{Style.RESET_ALL}
776-
• Active Model: {Fore.YELLOW}{model_manager.current_model}{Style.RESET_ALL}
777-
"""
778-
logger.info(log_message)
779-
return response
759+
# Define a log listener function in the parent to print messages from the log queue
780760

781-
# Define a helper function to tail the log file and print its content to stdout
782-
import time
783-
import threading
784-
785-
def tail_log_file(filepath):
786-
last_pos = 0
761+
def log_listener(queue):
787762
while True:
788763
try:
789-
with open(filepath, 'r') as f:
790-
f.seek(last_pos)
791-
new_content = f.read()
792-
if new_content:
793-
print(new_content, end='')
794-
last_pos = f.tell()
764+
msg = queue.get()
765+
if msg is None:
766+
break
767+
print(msg, end='')
795768
except Exception as e:
796769
pass
797-
time.sleep(1)
798770

799771
if __name__ == "__main__":
800772
import multiprocessing
@@ -804,7 +776,9 @@ def tail_log_file(filepath):
804776
logger.warning("multiprocessing start method already set: " + str(e))
805777

806778
import threading
807-
tail_thread = threading.Thread(target=tail_log_file, args=("server.log",), daemon=True)
808-
tail_thread.start()
809-
810-
start_server(use_ngrok=True)
779+
# Create a log queue and start the listener thread
780+
log_queue = multiprocessing.Queue()
781+
listener_thread = threading.Thread(target=log_listener, args=(log_queue,), daemon=True)
782+
listener_thread.start()
783+
784+
start_server(use_ngrok=True, log_queue=log_queue)

0 commit comments

Comments
 (0)