Text Generation
Transformers
English
qwen2
code-generation
python
fine-tuning
Qwen
tools
agent-framework
multi-agent
conversational
Eval Results (legacy)
Instructions to use my-ai-stack/Stack-2-9-finetuned with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use my-ai-stack/Stack-2-9-finetuned with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="my-ai-stack/Stack-2-9-finetuned") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("my-ai-stack/Stack-2-9-finetuned") model = AutoModelForCausalLM.from_pretrained("my-ai-stack/Stack-2-9-finetuned") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps
- vLLM
How to use my-ai-stack/Stack-2-9-finetuned with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "my-ai-stack/Stack-2-9-finetuned" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "my-ai-stack/Stack-2-9-finetuned", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/my-ai-stack/Stack-2-9-finetuned
- SGLang
How to use my-ai-stack/Stack-2-9-finetuned with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "my-ai-stack/Stack-2-9-finetuned" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "my-ai-stack/Stack-2-9-finetuned", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "my-ai-stack/Stack-2-9-finetuned" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "my-ai-stack/Stack-2-9-finetuned", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use my-ai-stack/Stack-2-9-finetuned with Docker Model Runner:
docker model run hf.co/my-ai-stack/Stack-2-9-finetuned
walidsobhie-code Claude Opus 4.6 commited on
Commit ·
d083607
1
Parent(s): 239da7a
docs: Add official launch plan
Browse files- Launch plan with phased steps
- Testing checklist
- Demo setup instructions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- LAUNCH_CHECKLIST.md +164 -0
- LAUNCH_PLAN.md +196 -0
- README.md +291 -312
- TOOLS.md +40 -0
- docs/pattern-moat.md +343 -0
- k8s/deployment.yaml +322 -0
- k8s/pvc.yaml +87 -0
- k8s/secret.yaml +52 -0
- k8s/service.yaml +80 -0
- runpod_deploy.sh +201 -0
- scripts/extract_patterns_from_git.py +259 -407
- scripts/merge_lora_adapters.py +241 -0
- vastai_deploy.sh +288 -0
LAUNCH_CHECKLIST.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stack 2.9 Official Launch Checklist
|
| 2 |
+
|
| 3 |
+
This document outlines the steps to officially launch Stack 2.9.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Phase 1: Testing & Validation
|
| 8 |
+
|
| 9 |
+
### ✅ 1.1 Run Unit Tests
|
| 10 |
+
```bash
|
| 11 |
+
cd stack-2.9
|
| 12 |
+
python -m pytest samples/ -v
|
| 13 |
+
```
|
| 14 |
+
|
| 15 |
+
### ✅ 1.2 Test Model Inference
|
| 16 |
+
```bash
|
| 17 |
+
# Test with Ollama (local)
|
| 18 |
+
python stack/eval/simple_test.py
|
| 19 |
+
|
| 20 |
+
# Or test with OpenAI
|
| 21 |
+
python stack/eval/simple_test.py --provider openai
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
### ⏳ 1.3 Run Benchmarks (Required)
|
| 25 |
+
```bash
|
| 26 |
+
# Download datasets
|
| 27 |
+
python scripts/download_benchmark_datasets.py
|
| 28 |
+
|
| 29 |
+
# Run HumanEval
|
| 30 |
+
python stack/eval/run_proper_evaluation.py --benchmark humaneval --output results/
|
| 31 |
+
|
| 32 |
+
# Run MBPP
|
| 33 |
+
python stack/eval/run_proper_evaluation.py --benchmark mbpp --output results/
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
### ⏳ 1.4 Test Deployment
|
| 37 |
+
```bash
|
| 38 |
+
# Test Docker locally
|
| 39 |
+
cd stack/deploy
|
| 40 |
+
docker build -t stack-2.9 .
|
| 41 |
+
docker run -p 8000:8000 stack-2.9
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## Phase 2: Model Preparation
|
| 47 |
+
|
| 48 |
+
### ⏳ 2.1 Fine-tune Model
|
| 49 |
+
```bash
|
| 50 |
+
# Option 1: Together AI (free credits)
|
| 51 |
+
python stack/training/together_finetune.py --model 7b --data data/final/train.jsonl
|
| 52 |
+
|
| 53 |
+
# Option 2: Google Colab
|
| 54 |
+
# Open colab_train_stack29.ipynb
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### ⏳ 2.2 Quantize Model (for deployment)
|
| 58 |
+
```bash
|
| 59 |
+
python stack/training/quantize_awq.py \
|
| 60 |
+
--model Qwen/Qwen2.5-Coder-7B \
|
| 61 |
+
--output stack/deploy/models/
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
### ⏳ 2.3 Upload to HuggingFace
|
| 65 |
+
```bash
|
| 66 |
+
python -c "
|
| 67 |
+
from huggingface_hub import HfApi
|
| 68 |
+
api = HfApi()
|
| 69 |
+
api.upload_folder(
|
| 70 |
+
folder_path='./stack/deploy/models',
|
| 71 |
+
repo_id='yourusername/stack-2.9-7b',
|
| 72 |
+
repo_type='model'
|
| 73 |
+
)
|
| 74 |
+
"
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
## Phase 3: Deployment
|
| 80 |
+
|
| 81 |
+
### ⏳ 3.1 Deploy to HuggingFace Spaces (Free)
|
| 82 |
+
```bash
|
| 83 |
+
# 1. Create space: https://huggingface.co/spaces/new
|
| 84 |
+
# 2. Choose: Docker, Python 3.11
|
| 85 |
+
# 3. Push files:
|
| 86 |
+
git clone https://huggingface.co/spaces/yourusername/stack-2.9
|
| 87 |
+
cp stack/deploy/hfSpaces/* .
|
| 88 |
+
git add . && git push
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### ⏳ 3.2 Create Demo UI (Gradio)
|
| 92 |
+
```bash
|
| 93 |
+
# Already included in hfSpaces/app.py
|
| 94 |
+
# Access at: https://your-space.hf.space
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
---
|
| 98 |
+
|
| 99 |
+
## Phase 4: Documentation & Launch
|
| 100 |
+
|
| 101 |
+
### ⏳ 4.1 Final Documentation Check
|
| 102 |
+
- [ ] README.md complete
|
| 103 |
+
- [ ] FREE_DEPLOYMENT.md complete
|
| 104 |
+
- [ ] API documentation in stack/docs/
|
| 105 |
+
- [ ] Examples in samples/
|
| 106 |
+
|
| 107 |
+
### ⏳ 4.2 Create Release
|
| 108 |
+
```bash
|
| 109 |
+
# Tag the release
|
| 110 |
+
git tag v1.0.0
|
| 111 |
+
git push origin v1.0.0
|
| 112 |
+
|
| 113 |
+
# Create GitHub release with:
|
| 114 |
+
# - Release notes
|
| 115 |
+
# - Model download links
|
| 116 |
+
# - Demo links
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
### ⏳ 4.3 Submit to Platforms
|
| 120 |
+
- [ ] Submit to OpenRouter (API listing)
|
| 121 |
+
- [ ] Submit to HuggingFace (model + Space)
|
| 122 |
+
- [ ] Add to LangChain integrations (optional)
|
| 123 |
+
|
| 124 |
+
---
|
| 125 |
+
|
| 126 |
+
## Phase 5: Promotion
|
| 127 |
+
|
| 128 |
+
### ⏳ 5.1 Social Media
|
| 129 |
+
- [ ] Announce on Twitter/X
|
| 130 |
+
- [ ] Post on LinkedIn
|
| 131 |
+
- [ ] Share on AI Discord servers
|
| 132 |
+
|
| 133 |
+
### ⏳ 5.2 Community
|
| 134 |
+
- [ ] Create Discord server
|
| 135 |
+
- [ ] Add to awesome lists
|
| 136 |
+
- [ ] Submit to Product Hunt
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## Quick Start (If Everything Ready)
|
| 141 |
+
|
| 142 |
+
```bash
|
| 143 |
+
# 1. Test locally
|
| 144 |
+
python stack/eval/simple_test.py
|
| 145 |
+
|
| 146 |
+
# 2. Deploy to HF Spaces
|
| 147 |
+
# (manual - see Phase 3)
|
| 148 |
+
|
| 149 |
+
# 3. Create release
|
| 150 |
+
git tag v1.0.0 && git push origin v1.0.0
|
| 151 |
+
```
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
## Current Status
|
| 156 |
+
|
| 157 |
+
| Item | Status |
|
| 158 |
+
|------|--------|
|
| 159 |
+
| Unit Tests | ✅ Ready (in samples/) |
|
| 160 |
+
| Inference Test | ✅ Ready |
|
| 161 |
+
| Benchmarks | ⏳ Need to run |
|
| 162 |
+
| Model Fine-tuned | ⏳ Need to do |
|
| 163 |
+
| Deployment | ⏳ Need to deploy |
|
| 164 |
+
| Release | ⏳ Need to create |
|
LAUNCH_PLAN.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stack 2.9 Official Launch Plan
|
| 2 |
+
|
| 3 |
+
This document outlines the steps to officially release Stack 2.9.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Phase 1: Testing & Validation (Immediate)
|
| 8 |
+
|
| 9 |
+
### 1.1 Unit Tests
|
| 10 |
+
```bash
|
| 11 |
+
# Run existing tests
|
| 12 |
+
cd /Users/walidsobhi/.openclaw/workspace/stack-2.9
|
| 13 |
+
python -m pytest samples/ -v
|
| 14 |
+
|
| 15 |
+
# Expected: All tests pass
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
### 1.2 Integration Tests
|
| 19 |
+
```bash
|
| 20 |
+
# Test CLI functionality
|
| 21 |
+
python -m pytest samples/integration/ -v
|
| 22 |
+
|
| 23 |
+
# Test tools
|
| 24 |
+
python -m pytest samples/unit/test_tools.py -v
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
### 1.3 Model Benchmark
|
| 28 |
+
```bash
|
| 29 |
+
# Download benchmark datasets
|
| 30 |
+
python scripts/download_benchmark_datasets.py --data-dir ./data
|
| 31 |
+
|
| 32 |
+
# Run HumanEval (164 problems)
|
| 33 |
+
python stack/eval/run_proper_evaluation.py \
|
| 34 |
+
--benchmark humaneval \
|
| 35 |
+
--provider ollama \
|
| 36 |
+
--model qwen2.5-coder:7b \
|
| 37 |
+
--k-samples 10 \
|
| 38 |
+
--output-dir ./results
|
| 39 |
+
|
| 40 |
+
# Run MBPP (500 problems)
|
| 41 |
+
python stack/eval/run_proper_evaluation.py \
|
| 42 |
+
--benchmark mbpp \
|
| 43 |
+
--provider ollama \
|
| 44 |
+
--model qwen2.5-coder:7b \
|
| 45 |
+
--k-samples 10 \
|
| 46 |
+
--output-dir ./results
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### 1.4 Quick Smoke Test
|
| 50 |
+
```bash
|
| 51 |
+
# Test basic functionality
|
| 52 |
+
python stack/eval/simple_test.py
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
---
|
| 56 |
+
|
| 57 |
+
## Phase 2: Demo & Showcase (Day 1-2)
|
| 58 |
+
|
| 59 |
+
### 2.1 Create Working Demo
|
| 60 |
+
```bash
|
| 61 |
+
# Create a simple Gradio demo
|
| 62 |
+
cd stack/deploy
|
| 63 |
+
python app.py # Should start web interface
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
### 2.2 Record Demo Video
|
| 67 |
+
- Show voice input/output
|
| 68 |
+
- Show code generation
|
| 69 |
+
- Show tool usage
|
| 70 |
+
|
| 71 |
+
### 2.3 Create Screenshots
|
| 72 |
+
- CLI interface
|
| 73 |
+
- Web UI
|
| 74 |
+
- API responses
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## Phase 3: Documentation Finalization (Day 2-3)
|
| 79 |
+
|
| 80 |
+
### 3.1 Verify All Docs Present
|
| 81 |
+
```
|
| 82 |
+
README.md ✅ Main documentation
|
| 83 |
+
stack/deploy/FREE_DEPLOYMENT.md ✅ Free deployment guide
|
| 84 |
+
stack/deploy/README.md ✅ Deployment docs
|
| 85 |
+
DIRECTORY_STRUCTURE.md ✅ Project structure
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
### 3.2 Update Version
|
| 89 |
+
```bash
|
| 90 |
+
# Update version in files
|
| 91 |
+
- README.md
|
| 92 |
+
- pyproject.toml
|
| 93 |
+
- package.json
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## Phase 4: Deployment Setup (Day 3-4)
|
| 99 |
+
|
| 100 |
+
### 4.1 HuggingFace Space
|
| 101 |
+
1. Create account at huggingface.co
|
| 102 |
+
2. New Space → Docker → Python 3.11
|
| 103 |
+
3. Push `stack/deploy/hfSpaces/*`
|
| 104 |
+
4. Get public URL
|
| 105 |
+
|
| 106 |
+
### 4.2 Model Upload
|
| 107 |
+
```bash
|
| 108 |
+
# Upload fine-tuned model
|
| 109 |
+
python stack/training/upload_hf.py \
|
| 110 |
+
--model-path ./output/stack-2.9-7b \
|
| 111 |
+
--repo-id yourusername/stack-2.9-7b
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
### 4.3 Test Free Deployment
|
| 115 |
+
```bash
|
| 116 |
+
# Test on free tier
|
| 117 |
+
cd stack/deploy/hfSpaces
|
| 118 |
+
docker build -t stack-2.9 .
|
| 119 |
+
docker run -p 7860:7860 stack-2.9
|
| 120 |
+
```
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
+
## Phase 5: Launch & Promote (Day 5-7)
|
| 125 |
+
|
| 126 |
+
### 5.1 Social Media
|
| 127 |
+
- Twitter/X thread
|
| 128 |
+
- LinkedIn post
|
| 129 |
+
- Hacker News submission
|
| 130 |
+
- Reddit r/LocalLLaMA
|
| 131 |
+
|
| 132 |
+
### 5.2 Platforms
|
| 133 |
+
- Submit to [OpenRouter](https://openrouter.ai/)
|
| 134 |
+
- Submit to [HuggingFace](https://huggingface.co/)
|
| 135 |
+
- Add to [awesome-llm](https://github.com/Hannibal046/Awesome-LLM) list
|
| 136 |
+
|
| 137 |
+
### 5.3 Community
|
| 138 |
+
- Discord server invite
|
| 139 |
+
- GitHub discussions
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Launch Checklist
|
| 144 |
+
|
| 145 |
+
| Task | Status | Notes |
|
| 146 |
+
|------|--------|-------|
|
| 147 |
+
| Unit tests pass | ⬜ | Run `pytest samples/` |
|
| 148 |
+
| Integration tests pass | ⬜ | Run `pytest samples/integration/` |
|
| 149 |
+
| Benchmarks run | ⬜ | HumanEval + MBPP |
|
| 150 |
+
| Demo works | ⬜ | Gradio UI test |
|
| 151 |
+
| Free deployment works | ⬜ | HF Spaces test |
|
| 152 |
+
| Documentation complete | ⬜ | All docs in place |
|
| 153 |
+
| Version updated | ⬜ | Set to 1.0.0 |
|
| 154 |
+
| HF Space deployed | ⬜ | Get public URL |
|
| 155 |
+
| Model uploaded | ⬜ | To HuggingFace |
|
| 156 |
+
| Social media ready | ⬜ | Posts prepared |
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## Quick Test Commands
|
| 161 |
+
|
| 162 |
+
```bash
|
| 163 |
+
# 1. Test imports
|
| 164 |
+
cd /Users/walidsobhi/.openclaw/workspace/stack-2.9
|
| 165 |
+
python -c "from stack.eval.model_client import create_model_client; print('OK')"
|
| 166 |
+
|
| 167 |
+
# 2. Test CLI
|
| 168 |
+
python -m stack.cli.cli --help
|
| 169 |
+
|
| 170 |
+
# 3. Test eval
|
| 171 |
+
python stack/eval/simple_test.py
|
| 172 |
+
|
| 173 |
+
# 4. Run benchmarks
|
| 174 |
+
python stack/eval/run_proper_evaluation.py --benchmark humaneval --provider ollama --model qwen2.5-coder:7b --k-samples 5
|
| 175 |
+
|
| 176 |
+
# 5. Start web UI
|
| 177 |
+
cd stack/deploy && python app.py
|
| 178 |
+
```
|
| 179 |
+
|
| 180 |
+
---
|
| 181 |
+
|
| 182 |
+
## Expected Outcomes
|
| 183 |
+
|
| 184 |
+
After launch:
|
| 185 |
+
- ✅ Working open-source AI coding assistant
|
| 186 |
+
- ✅ Free deployment on HF Spaces
|
| 187 |
+
- ✅ Fine-tunable on Together AI
|
| 188 |
+
- ✅ 46 tool schemas trained
|
| 189 |
+
- ✅ OpenAI-compatible API
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
## Contact & Support
|
| 194 |
+
|
| 195 |
+
- Issues: https://github.com/my-ai-stack/stack-2.9/issues
|
| 196 |
+
- Discussions: https://github.com/my-ai-stack/stack-2.9/discussions
|
README.md
CHANGED
|
@@ -1,335 +1,345 @@
|
|
| 1 |
<p align="center">
|
| 2 |
-
<
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
<
|
| 6 |
-
|
| 7 |
-
<
|
| 8 |
-
<img src="https://img.shields.io/badge/
|
| 9 |
-
<img src="https://img.shields.io/
|
| 10 |
-
<img src="https://img.shields.io/
|
|
|
|
| 11 |
</p>
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
# Stack 2.9 🤖
|
| 16 |
-
|
| 17 |
-
<p align="center">
|
| 18 |
-
<strong>The pattern-based AI coding assistant that improves through experience.</strong>
|
| 19 |
-
</p>
|
| 20 |
|
| 21 |
-
|
| 22 |
|
| 23 |
-
---
|
| 24 |
|
| 25 |
-
## ✨ Features
|
| 26 |
|
| 27 |
| Feature | Description |
|
| 28 |
|---------|-------------|
|
| 29 |
-
| **
|
| 30 |
-
| **
|
| 31 |
-
| **
|
| 32 |
-
| **
|
| 33 |
-
| **
|
| 34 |
-
| **
|
| 35 |
-
| **
|
| 36 |
-
| **🔧 46 Built-in Tools** | File ops, search, shell commands, git, voice tools, MCP tools |
|
| 37 |
-
| **🌐 Multi-Provider** | Works with Ollama, OpenAI, Anthropic, OpenRouter, Together AI — or bring your own model |
|
| 38 |
-
| **📱 Terminal UI** | Beautiful interactive CLI with chat, benchmarks, and training |
|
| 39 |
-
| **🔒 Self-Hosted** | Run locally, own your data, deploy anywhere |
|
| 40 |
|
| 41 |
-
|
| 42 |
|
| 43 |
-
##
|
| 44 |
|
| 45 |
-
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
|
| 52 |
|
| 53 |
-
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
|
|
|
| 56 |
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
|
| 59 |
-
|
| 60 |
-
2. **Reproducible runs**: Full logs, config files, and per-problem results
|
| 61 |
-
3. **Standard metrics**: Pass@1 with confidence intervals, using k≥100 samples
|
| 62 |
-
4. **Transparent methodology**: All code and data publicly available
|
| 63 |
|
| 64 |
-
|
| 65 |
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
# Run evaluation with a model provider
|
| 75 |
-
python stack_2_9_eval/run_proper_evaluation.py \
|
| 76 |
-
--benchmark humaneval \
|
| 77 |
-
--provider ollama \
|
| 78 |
-
--model qwen2.5-coder:32b \
|
| 79 |
-
--k-samples 100 \
|
| 80 |
-
--output-dir ./results/humaneval_run
|
| 81 |
```
|
| 82 |
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
python stack.py --eval all --provider ollama --eval-model qwen2.5-coder:32b
|
| 87 |
-
```
|
| 88 |
|
| 89 |
-
##
|
| 90 |
|
| 91 |
-
|
| 92 |
|
| 93 |
-
-
|
| 94 |
-
-
|
|
|
|
|
|
|
| 95 |
|
| 96 |
-
|
| 97 |
|
| 98 |
-
-
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
-
##
|
| 103 |
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
|
|
|
|
|
|
| 110 |
|
| 111 |
-
#
|
| 112 |
-
pip install -r requirements.txt
|
| 113 |
-
```
|
| 114 |
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
-
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|---------------|---------|-------------|------------|
|
| 121 |
-
| **GPU** | NVIDIA 8GB VRAM | NVIDIA 24GB VRAM | NVIDIA 40-80GB (A100/H100) |
|
| 122 |
-
| **RAM** | 16GB | 32GB | 64GB+ |
|
| 123 |
-
| **Disk** | 20GB free | 50GB free | 100GB+ (NVMe) |
|
| 124 |
-
| **CUDA** | 11.8 | 12.1 | 12.1+ |
|
| 125 |
-
| **Models** | 7B quantized | 32B quantized | 70B+ quantized |
|
| 126 |
|
| 127 |
-
**
|
| 128 |
-
- CPU-only mode is possible but extremely slow (not recommended for production)
|
| 129 |
-
- AWQ/GPTQ quantization reduces VRAM requirements by ~50%
|
| 130 |
-
- Multi-GPU (tensor parallelism) supported for large models
|
| 131 |
-
- Ensure NVIDIA drivers and CUDA toolkit are installed
|
| 132 |
|
| 133 |
-
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
|
| 136 |
|
| 137 |
-
|
| 138 |
-
|----------|-------------|-----|
|
| 139 |
-
| **HuggingFace Spaces** | 2CPU 4GB inference | `stack/deploy/FREE_DEPLOYMENT.md` |
|
| 140 |
-
| **Together AI** | Fine-tuning credits | `stack/training/together_finetune.py` |
|
| 141 |
-
| **Google Colab** | ~0.5hr GPU/day | `colab_train_stack29.ipynb` |
|
| 142 |
|
| 143 |
-
|
| 144 |
-
-
|
| 145 |
-
|
| 146 |
-
|
|
|
|
|
|
|
| 147 |
|
| 148 |
-
See
|
| 149 |
-
For paid deployment (Docker, RunPod, Vast.ai), see `stack/deploy/README.md`.
|
| 150 |
|
| 151 |
-
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
# Start the CLI
|
| 155 |
-
python stack.py
|
| 156 |
|
| 157 |
-
|
| 158 |
-
python -m stack_cli.cli
|
| 159 |
-
```
|
| 160 |
|
| 161 |
-
##
|
|
|
|
|
|
|
| 162 |
|
| 163 |
```bash
|
| 164 |
-
#
|
| 165 |
-
python stack.py
|
|
|
|
|
|
|
|
|
|
| 166 |
|
| 167 |
# Run benchmarks
|
| 168 |
python stack.py --eval all --provider ollama
|
| 169 |
-
python stack.py --eval mbpp --provider openai --model gpt-4o
|
| 170 |
|
| 171 |
-
#
|
| 172 |
python stack.py --patterns list
|
| 173 |
python stack.py --patterns stats
|
| 174 |
```
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
---
|
| 177 |
|
| 178 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
|
| 180 |
-
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
```
|
| 183 |
-
$ python stack.py
|
| 184 |
-
╔═══════════════════════════════════════════════════════════╗
|
| 185 |
-
║ Stack 2.9 - Pattern Memory AI ║
|
| 186 |
-
║ Your AI coding companion ║
|
| 187 |
-
╚═══════════════════════════════════════════════════════════╝
|
| 188 |
|
| 189 |
-
|
| 190 |
-
[1] Chat with Stack 2.9
|
| 191 |
-
[2] Run Evaluation
|
| 192 |
-
[3] Manage Patterns
|
| 193 |
-
[4] Train Model
|
| 194 |
-
[5] Settings
|
| 195 |
|
| 196 |
-
|
| 197 |
|
| 198 |
-
|
| 199 |
|
| 200 |
-
|
| 201 |
|
| 202 |
-
|
| 203 |
-
return s[::-1]
|
| 204 |
|
| 205 |
-
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
| 207 |
```
|
| 208 |
|
| 209 |
-
|
| 210 |
|
| 211 |
-
|
| 212 |
-
from stack_cli.cli import StackCLI
|
| 213 |
-
from stack_cli.agent import create_agent
|
| 214 |
|
| 215 |
-
#
|
| 216 |
-
agent = create_agent()
|
| 217 |
-
response = agent.process("Write a hello world in Python")
|
| 218 |
-
print(response.content)
|
| 219 |
|
| 220 |
-
|
| 221 |
-
from stack_2_9_eval.model_client import create_model_client
|
| 222 |
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
```
|
| 227 |
|
| 228 |
-
|
| 229 |
|
| 230 |
-
|
| 231 |
-
from stack_2_9_training.pattern_miner import PatternMiner
|
| 232 |
|
| 233 |
-
|
|
|
|
|
|
|
| 234 |
|
| 235 |
-
#
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
solution="return n * factorial(n-1)",
|
| 239 |
-
success=True
|
| 240 |
-
)
|
| 241 |
|
| 242 |
-
#
|
| 243 |
-
|
| 244 |
-
print(f"Found {len(patterns)} relevant patterns")
|
| 245 |
```
|
| 246 |
|
| 247 |
-
|
| 248 |
|
| 249 |
-
|
| 250 |
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
| 252 |
|
| 253 |
-
|
| 254 |
-
|-----------|--------|-------|
|
| 255 |
-
| **HumanEval** | Pending | Full 164-problem evaluation in progress |
|
| 256 |
-
| **MBPP** | Pending | Full 500-problem evaluation in progress |
|
| 257 |
-
| **Tool Use** | Pending | Custom tool-calling benchmark to be created |
|
| 258 |
-
| **GSM8K** | Not started | Math reasoning evaluation planned |
|
| 259 |
-
| **Context** | ✅ 128K | Token context window tested |
|
| 260 |
|
| 261 |
---
|
| 262 |
|
| 263 |
-
##
|
| 264 |
|
| 265 |
-
###
|
| 266 |
|
| 267 |
```bash
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
|
| 272 |
-
#
|
| 273 |
-
export MODEL_PROVIDER=openai
|
| 274 |
-
export OPENAI_API_KEY=sk-...
|
| 275 |
-
export OPENAI_MODEL=gpt-4o
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
# Optional: customize referer and title for OpenRouter dashboard
|
| 286 |
-
export HTTP_REFERER=https://your-app.com
|
| 287 |
-
export X_TITLE="Stack 2.9"
|
| 288 |
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
### Configuration File
|
| 296 |
|
|
|
|
|
|
|
| 297 |
```yaml
|
| 298 |
-
# stack.yaml
|
| 299 |
model:
|
| 300 |
provider: ollama
|
| 301 |
name: qwen2.5-coder:32b
|
|
|
|
| 302 |
|
| 303 |
training:
|
| 304 |
lora_rank: 16
|
| 305 |
learning_rate: 3e-4
|
|
|
|
| 306 |
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
- gsm8k
|
| 312 |
-
```
|
| 313 |
-
|
| 314 |
-
---
|
| 315 |
-
|
| 316 |
-
## 🏗️ Architecture
|
| 317 |
-
|
| 318 |
-
```
|
| 319 |
-
┌─────────────────────────────────────────────────────────────┐
|
| 320 |
-
│ Stack 2.9 CLI │
|
| 321 |
-
├─────────────────────────────────────────────────────────────┤
|
| 322 |
-
│ chat_mode │ eval_mode │ pattern_mode │ train │
|
| 323 |
-
├─────────────────────────────────────────────────────────────┤
|
| 324 |
-
│ Model Client Layer │
|
| 325 |
-
│ OllamaClient │ OpenAIClient │ AnthropicClient │ OpenRouterClient │ TogetherClient │
|
| 326 |
-
├─────────────────────────────────────────────────────────────┤
|
| 327 |
-
│ Self-Evolution Layer │
|
| 328 |
-
│ pattern_miner │ data_quality │ train_lora │
|
| 329 |
-
├─────────────────────────────────────────────────────────────┤
|
| 330 |
-
│ Base Model │
|
| 331 |
-
│ Qwen2.5-Coder-32B (or your model) │
|
| 332 |
-
└─────────────────────────────────────────────────────────────┘
|
| 333 |
```
|
| 334 |
|
| 335 |
---
|
|
@@ -338,122 +348,91 @@ eval:
|
|
| 338 |
|
| 339 |
```
|
| 340 |
stack-2.9/
|
| 341 |
-
├── stack_cli/
|
| 342 |
-
│ ├── cli.py
|
| 343 |
-
│ ├── agent.py
|
| 344 |
-
│ └── context.py
|
| 345 |
│
|
| 346 |
-
├── stack_2_9_eval/
|
| 347 |
-
│ ├── model_client.py
|
| 348 |
-
│ └── benchmarks/
|
| 349 |
│
|
| 350 |
-
├── stack_2_9_training/
|
| 351 |
-
│ ├──
|
| 352 |
-
│ ├──
|
| 353 |
-
│ └──
|
| 354 |
│
|
| 355 |
-
├── stack_2_9_deploy/
|
| 356 |
-
│
|
|
|
|
| 357 |
│
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
#
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
#
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
### Training
|
| 378 |
-
|
| 379 |
-
```bash
|
| 380 |
-
# Prepare data
|
| 381 |
-
python -m stack_2_9_training.prepare_data
|
| 382 |
-
|
| 383 |
-
# Train LoRA
|
| 384 |
-
python -m stack_2_9_training.train_lora --config train_config.yaml
|
| 385 |
-
|
| 386 |
-
# Merge adapter
|
| 387 |
-
python -m stack_2_9_training.merge_adapter --base-model qwen2.5-coder-32b
|
| 388 |
```
|
| 389 |
|
| 390 |
---
|
| 391 |
|
| 392 |
-
##
|
| 393 |
|
| 394 |
-
|
| 395 |
-
# Quick start with Docker
|
| 396 |
-
cd stack-2.9-deploy
|
| 397 |
-
docker-compose up -d
|
| 398 |
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
``
|
|
|
|
|
|
|
| 402 |
|
| 403 |
---
|
| 404 |
|
| 405 |
-
##
|
| 406 |
|
| 407 |
-
|
| 408 |
-
- [Architecture](stack-2.9-docs/ARCHITECTURE.md)
|
| 409 |
-
- [Setup Guide](stack-2.9-docs/SETUP.md)
|
| 410 |
-
- [Contributing](CONTRIBUTING.md)
|
| 411 |
|
| 412 |
-
|
| 413 |
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
1. Fork the repository
|
| 419 |
-
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
| 420 |
-
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
| 421 |
-
4. Push to the branch (`git push origin feature/amazing-feature`)
|
| 422 |
-
5. Open a Pull Request
|
| 423 |
|
| 424 |
---
|
| 425 |
|
| 426 |
-
##
|
| 427 |
|
| 428 |
-
|
|
|
|
|
|
|
|
|
|
| 429 |
|
| 430 |
---
|
| 431 |
|
| 432 |
-
##
|
| 433 |
|
| 434 |
-
- [
|
| 435 |
-
- [
|
| 436 |
-
- [
|
|
|
|
|
|
|
|
|
|
| 437 |
|
| 438 |
---
|
| 439 |
|
| 440 |
<p align="center">
|
| 441 |
Built with ❤️ for developers who want an AI that grows with them
|
| 442 |
</p>
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
### Free Deployment (No Cost)
|
| 446 |
-
|
| 447 |
-
Stack 2.9 can run on free platforms:
|
| 448 |
-
|
| 449 |
-
| Platform | What's Free | Recommended For |
|
| 450 |
-
|----------|-----------------|-----------------|
|
| 451 |
-
| **HuggingFace Spaces** | 2CPU 4GB hosting | API deployment |
|
| 452 |
-
| **Together AI** | Fine-tuning credits | Model customization |
|
| 453 |
-
| **Google Colab** | ~0.5hr GPU/day | Training experiments |
|
| 454 |
-
|
| 455 |
-
**Free tier model:** Use Qwen2.5-Coder-7B (runs on free GPU)
|
| 456 |
-
|
| 457 |
-
See `stack/deploy/FREE_DEPLOYMENT.md` for detailed guide.
|
| 458 |
-
|
| 459 |
-
For paid options see `stack/deploy/README.md`.
|
|
|
|
| 1 |
<p align="center">
|
| 2 |
+
<a href="https://github.com/my-ai-stack/stack-2.9">
|
| 3 |
+
<img src="https://img.shields.io/github/stars/my-ai-stack/stack-2.9?style=flat-square" alt="GitHub stars"/>
|
| 4 |
+
</a>
|
| 5 |
+
<a href="https://github.com/my-ai-stack/stack-2.9/blob/main/LICENSE">
|
| 6 |
+
<img src="https://img.shields.io/github/license/my-ai-stack/stack-2.9?style=flat-square&logo=apache" alt="License"/>
|
| 7 |
+
</a>
|
| 8 |
+
<img src="https://img.shields.io/badge/OpenRouter-Compatible-green?style=flat-square&logo=openrouter" alt="OpenRouter"/>
|
| 9 |
+
<img src="https://img.shields.io/badge/Together_AI-Ready-green?style=flat-square&logo=databricks" alt="Together AI"/>
|
| 10 |
+
<img src="https://img.shields.io/badge/Hugging%20Face-Model-green?style=flat-square&logo=huggingface" alt="Hugging Face"/>
|
| 11 |
+
<img src="https://img.shields.io/badge/Python-3.10+-blue?style=flat-square&logo=python" alt="Python 3.10+"/>
|
| 12 |
</p>
|
| 13 |
|
| 14 |
+
# Stack 2.9
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
+
> **The pattern-based AI coding assistant that improves through experience.**
|
| 17 |
|
| 18 |
+
Stack 2.9 is an open-source AI coding assistant powered by **Qwen2.5-Coder-32B**, enhanced with **Pattern Memory** — a system that learns from interactions by storing successful patterns and retrieving them for future tasks.
|
| 19 |
|
| 20 |
+
## ✨ Key Features
|
| 21 |
|
| 22 |
| Feature | Description |
|
| 23 |
|---------|-------------|
|
| 24 |
+
| **Pattern Memory** | Stores and retrieves successful coding patterns, becoming more helpful over time |
|
| 25 |
+
| **Multi-Provider** | Works with Ollama, OpenAI, Anthropic, OpenRouter, Together AI |
|
| 26 |
+
| **46 Built-in Tools** | File ops, git, shell, web search, memory, task planning |
|
| 27 |
+
| **Voice Integration** | Coqui XTTS for voice cloning, STT for voice input |
|
| 28 |
+
| **128K Context** | Handles large codebases with ease |
|
| 29 |
+
| **Self-Hosted** | Full control, your data stays private |
|
| 30 |
+
| **MCP Support** | Integrates with any Model Context Protocol server |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
---
|
| 33 |
|
| 34 |
+
## 🚀 Quick Start
|
| 35 |
|
| 36 |
+
### Installation
|
| 37 |
|
| 38 |
+
```bash
|
| 39 |
+
git clone https://github.com/my-ai-stack/stack-2.9.git
|
| 40 |
+
cd stack-2.9
|
| 41 |
+
pip install -r requirements.txt
|
| 42 |
+
```
|
| 43 |
|
| 44 |
+
### Basic Usage
|
| 45 |
|
| 46 |
+
```bash
|
| 47 |
+
# Start interactive chat
|
| 48 |
+
python stack.py
|
| 49 |
|
| 50 |
+
# Single query
|
| 51 |
+
python stack.py -c "Write a Python function to reverse a string"
|
| 52 |
|
| 53 |
+
# Run evaluation (requires datasets)
|
| 54 |
+
python stack.py --eval humaneval --provider ollama
|
| 55 |
+
```
|
| 56 |
|
| 57 |
+
### Configure Model Provider
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
Set environment variables before running:
|
| 60 |
|
| 61 |
+
```bash
|
| 62 |
+
# For Ollama (local, recommended)
|
| 63 |
+
export MODEL_PROVIDER=ollama
|
| 64 |
+
export OLLAMA_MODEL=qwen2.5-coder:32b
|
| 65 |
|
| 66 |
+
# For OpenAI
|
| 67 |
+
export MODEL_PROVIDER=openai
|
| 68 |
+
export OPENAI_API_KEY=sk-...
|
| 69 |
+
export OPENAI_MODEL=gpt-4o
|
| 70 |
|
| 71 |
+
# For Together AI (recommended for Qwen)
|
| 72 |
+
export MODEL_PROVIDER=together
|
| 73 |
+
export TOGETHER_API_KEY=tog-...
|
| 74 |
+
export TOGETHER_MODEL=togethercomputer/qwen2.5-coder-32b-instruct
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
```
|
| 76 |
|
| 77 |
+
See [Configuration](#⚙️-configuration) for all options.
|
| 78 |
|
| 79 |
+
---
|
|
|
|
|
|
|
| 80 |
|
| 81 |
+
## 🏗️ Model Card
|
| 82 |
|
| 83 |
+
### Base Model
|
| 84 |
|
| 85 |
+
- **Architecture:** Qwen2.5-Coder-32B (32 billion parameters)
|
| 86 |
+
- **Fine-tuning:** LoRA (Low-Rank Adaptation)
|
| 87 |
+
- **Context Length:** 131,072 tokens
|
| 88 |
+
- **Quantization:** 4-bit AWQ optional for efficient deployment
|
| 89 |
|
| 90 |
+
### Training Data
|
| 91 |
|
| 92 |
+
Stack 2.9 is fine-tuned on a diverse dataset including:
|
| 93 |
|
| 94 |
+
- **Pattern Memory Data** (5K-10K examples): Successful interaction logs with feedback
|
| 95 |
+
- **Synthetic Tool Examples** (20K+): Generated scenarios covering all 46 tools
|
| 96 |
+
- **Public Datasets**:
|
| 97 |
+
- OpenAssistant (coding conversations)
|
| 98 |
+
- CodeAct (executable actions)
|
| 99 |
+
- CodeContests (competition problems)
|
| 100 |
+
- StarCoder Data (permissively licensed code)
|
| 101 |
|
| 102 |
+
All data undergoes:
|
| 103 |
+
- Deduplication
|
| 104 |
+
- License compatibility check
|
| 105 |
+
- Quality filtering (length, validity, success rate)
|
| 106 |
|
| 107 |
+
### Intended Use
|
| 108 |
|
| 109 |
+
✅ **Allowed:**
|
| 110 |
+
- AI-assisted coding and code completion
|
| 111 |
+
- Code explanation and documentation
|
| 112 |
+
- Debugging and error analysis
|
| 113 |
+
- Tool-use automation
|
| 114 |
+
- Educational purposes
|
| 115 |
+
- Research on pattern-based AI
|
| 116 |
|
| 117 |
+
❌ **Not Recommended:**
|
| 118 |
+
- High-stakes production code without human review
|
| 119 |
+
- Security-critical applications
|
| 120 |
+
- Medical, legal, or financial decision-making
|
| 121 |
+
- Generating harmful or malicious code
|
| 122 |
+
- Large-scale redistribution without compliance checks
|
| 123 |
|
| 124 |
+
### Limitations
|
|
|
|
|
|
|
| 125 |
|
| 126 |
+
- **Hallucinations:** May generate incorrect code; always verify with tests
|
| 127 |
+
- **Security:** Can suggest vulnerable code; security review required for production
|
| 128 |
+
- **Licensing:** May reproduce copyrighted snippets; use license checks
|
| 129 |
+
- **Tool Dependencies:** Full functionality requires OpenClaw framework
|
| 130 |
+
- **Pattern Freshness:** Initial deployments have limited pattern library
|
| 131 |
|
| 132 |
+
---
|
| 133 |
|
| 134 |
+
## 📊 Benchmarks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
+
⚠️ **Important:** The benchmark scores previously listed in this README have been **removed pending verification**. An audit revealed:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
+
- HumanEval & MBPP implementations only had 20 problems (1-4% of full benchmarks)
|
| 139 |
+
- No proper inference logs exist for claimed numbers
|
| 140 |
+
- Tool Use evaluation lacked proper implementation
|
| 141 |
|
| 142 |
+
These scores were **unverifiable** and have been removed.
|
| 143 |
|
| 144 |
+
### Current Status
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
+
| Benchmark | Status | Notes |
|
| 147 |
+
|-----------|--------|-------|
|
| 148 |
+
| **HumanEval** | Evaluation in progress | Full 164-problem suite |
|
| 149 |
+
| **MBPP** | Evaluation in progress | Full 500-problem suite |
|
| 150 |
+
| **Tool Use** | Benchmark development | Custom tool-calling task |
|
| 151 |
+
| **GSM8K** | Not started | Math reasoning (optional) |
|
| 152 |
|
| 153 |
+
We are rebuilding evaluation infrastructure with proper methodology. See [EVALUATION.md](EVALUATION.md) for the audit report and plan.
|
|
|
|
| 154 |
|
| 155 |
+
**Expected baseline** (based on Qwen2.5-Coder-32B):
|
| 156 |
+
- HumanEval: ~70-72% Pass@1
|
| 157 |
+
- MBPP: ~75-77% Pass@1
|
| 158 |
|
| 159 |
+
Actual fine-tuned results will be published after proper evaluation.
|
|
|
|
|
|
|
| 160 |
|
| 161 |
+
---
|
|
|
|
|
|
|
| 162 |
|
| 163 |
+
## 💻 Usage
|
| 164 |
+
|
| 165 |
+
### Command Line Interface
|
| 166 |
|
| 167 |
```bash
|
| 168 |
+
# Interactive chat mode
|
| 169 |
+
python stack.py
|
| 170 |
+
|
| 171 |
+
# Single query
|
| 172 |
+
python stack.py -c "Explain this code..."
|
| 173 |
|
| 174 |
# Run benchmarks
|
| 175 |
python stack.py --eval all --provider ollama
|
|
|
|
| 176 |
|
| 177 |
+
# Manage patterns
|
| 178 |
python stack.py --patterns list
|
| 179 |
python stack.py --patterns stats
|
| 180 |
```
|
| 181 |
|
| 182 |
+
### Python API
|
| 183 |
+
|
| 184 |
+
```python
|
| 185 |
+
from stack_cli.agent import create_agent
|
| 186 |
+
|
| 187 |
+
# Create agent
|
| 188 |
+
agent = create_agent()
|
| 189 |
+
|
| 190 |
+
# Chat
|
| 191 |
+
response = agent.process("Write a hello world function")
|
| 192 |
+
print(response.content)
|
| 193 |
+
|
| 194 |
+
# Use tools
|
| 195 |
+
result = agent.process("List files in current directory")
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
### Available Tools
|
| 199 |
+
|
| 200 |
+
Stack 2.9 includes **46 built-in tools** for:
|
| 201 |
+
- File operations (read, write, edit, search, grep, copy, move, delete)
|
| 202 |
+
- Git operations (status, commit, push, pull, branch, log, diff)
|
| 203 |
+
- Code execution (run, test, lint, format, typecheck, server, install)
|
| 204 |
+
- Web (search, fetch, download, check_url, screenshot)
|
| 205 |
+
- Memory (recall, save, list, context_load, project_scan)
|
| 206 |
+
- Task planning (create_task, list_tasks, update_task, delete_task, create_plan, execute_plan)
|
| 207 |
+
|
| 208 |
+
See [TOOLS.md](TOOLS.md) for complete documentation with examples.
|
| 209 |
+
|
| 210 |
---
|
| 211 |
|
| 212 |
+
## 🔄 Pattern Memory Evolution
|
| 213 |
+
|
| 214 |
+
Stack 2.9's Pattern Memory can **evolve** automatically:
|
| 215 |
+
|
| 216 |
+
### Auto-Extraction from Git
|
| 217 |
|
| 218 |
+
Mine your Git history for patterns:
|
| 219 |
|
| 220 |
+
```bash
|
| 221 |
+
python scripts/extract_patterns_from_git.py \
|
| 222 |
+
--repo-path . \
|
| 223 |
+
--output patterns.jsonl \
|
| 224 |
+
--since-date "2024-01-01"
|
| 225 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
| 227 |
+
See `docs/pattern-moat.md` for details.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
+
### Team Sync (Shared Database)
|
| 230 |
|
| 231 |
+
Multiple developers can share patterns via a central PostgreSQL + FastAPI service. Schema and API endpoints documented in `docs/pattern-moat.md`.
|
| 232 |
|
| 233 |
+
### Weight Fusion
|
| 234 |
|
| 235 |
+
Merge LoRA adapters from multiple users with success-rate-weighted averaging:
|
|
|
|
| 236 |
|
| 237 |
+
```bash
|
| 238 |
+
python scripts/merge_lora_adapters.py \
|
| 239 |
+
--adapters adapter_a.safetensors adapter_b.safetensors \
|
| 240 |
+
--weights 0.7 0.3 \
|
| 241 |
+
--output merged.safetensors
|
| 242 |
```
|
| 243 |
|
| 244 |
+
---
|
| 245 |
|
| 246 |
+
## 🛠️ Training & Fine-Tuning
|
|
|
|
|
|
|
| 247 |
|
| 248 |
+
### Quick Training (Colab)
|
|
|
|
|
|
|
|
|
|
| 249 |
|
| 250 |
+
Use the provided notebook for quick prototyping:
|
|
|
|
| 251 |
|
| 252 |
+
```bash
|
| 253 |
+
# Open in Google Colab
|
| 254 |
+
colab_train_stack29.ipynb
|
| 255 |
```
|
| 256 |
|
| 257 |
+
Trains a 5K-example mini dataset in 3-5 hours on free T4 GPU.
|
| 258 |
|
| 259 |
+
### Full Training Pipeline
|
|
|
|
| 260 |
|
| 261 |
+
```bash
|
| 262 |
+
# Prepare data (from your sources)
|
| 263 |
+
python scripts/create_mini_dataset.py --size 5000 --output data_mini/train.jsonl
|
| 264 |
|
| 265 |
+
# Train LoRA adapter
|
| 266 |
+
cd stack_2_9_training
|
| 267 |
+
python -m train_lora --config train_config.yaml
|
|
|
|
|
|
|
|
|
|
| 268 |
|
| 269 |
+
# Merge adapter with base model
|
| 270 |
+
python -m merge_adapter --base-model Qwen/Qwen2.5-Coder-32B
|
|
|
|
| 271 |
```
|
| 272 |
|
| 273 |
+
### Cloud Training Scripts
|
| 274 |
|
| 275 |
+
For production training on GPUs:
|
| 276 |
|
| 277 |
+
- **RunPod:** `runpod_deploy.sh` — launches A100-80GB instances
|
| 278 |
+
- **Vast.ai:** `vastai_deploy.sh` — finds cheapest suitable instances
|
| 279 |
+
- **Kubernetes:** `k8s/deployment.yaml` — deploy to your K8s cluster
|
| 280 |
+
- **Docker:** `docker-compose.cloud.yaml` — bare-metal GPU servers
|
| 281 |
|
| 282 |
+
See each script for usage instructions.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
|
| 284 |
---
|
| 285 |
|
| 286 |
+
## 🐳 Deployment
|
| 287 |
|
| 288 |
+
### Docker (Local/Cloud)
|
| 289 |
|
| 290 |
```bash
|
| 291 |
+
cd stack-2.9-deploy
|
| 292 |
+
docker-compose up -d
|
| 293 |
+
```
|
| 294 |
|
| 295 |
+
### Cloud Platforms
|
|
|
|
|
|
|
|
|
|
| 296 |
|
| 297 |
+
| Platform | Use Case | Documentation |
|
| 298 |
+
|----------|----------|---------------|
|
| 299 |
+
| **RunPod** | Pay-as-you-go GPU | `runpod_deploy.sh` |
|
| 300 |
+
| **Vast.ai** | Spot instances (cheap) | `vastai_deploy.sh` |
|
| 301 |
+
| **Kubernetes** | Enterprise scale | `k8s/` directory |
|
| 302 |
+
| **HuggingFace Spaces** | Free inference hosting | `docs/free-deployment.md` |
|
| 303 |
|
| 304 |
+
**Hardware requirements:**
|
| 305 |
+
- **7B model:** RTX 3070 (8GB) minimum
|
| 306 |
+
- **32B model:** A100-40GB recommended
|
| 307 |
+
- **Quantized:** 4-bit reduces VRAM by ~50%
|
|
|
|
|
|
|
|
|
|
| 308 |
|
| 309 |
+
---
|
| 310 |
+
|
| 311 |
+
## 🔧 Configuration
|
| 312 |
+
|
| 313 |
+
### Environment Variables
|
| 314 |
+
|
| 315 |
+
| Variable | Required | Description |
|
| 316 |
+
|----------|----------|-------------|
|
| 317 |
+
| `MODEL_PROVIDER` | Yes | `ollama`, `openai`, `anthropic`, `openrouter`, `together` |
|
| 318 |
+
| `OPENAI_API_KEY` | If OpenAI | Your OpenAI API key |
|
| 319 |
+
| `ANTHROPIC_API_KEY` | If Anthropic | Your Anthropic API key |
|
| 320 |
+
| `OPENROUTER_API_KEY` | If OpenRouter | Your OpenRouter API key |
|
| 321 |
+
| `TOGETHER_API_KEY` | If Together | Your Together AI API key |
|
| 322 |
+
| `OLLAMA_MODEL` | If Ollama | Model name (e.g., `qwen2.5-coder:32b`) |
|
| 323 |
|
| 324 |
### Configuration File
|
| 325 |
|
| 326 |
+
Create `stack.yaml` in project root:
|
| 327 |
+
|
| 328 |
```yaml
|
|
|
|
| 329 |
model:
|
| 330 |
provider: ollama
|
| 331 |
name: qwen2.5-coder:32b
|
| 332 |
+
temperature: 0.7
|
| 333 |
|
| 334 |
training:
|
| 335 |
lora_rank: 16
|
| 336 |
learning_rate: 3e-4
|
| 337 |
+
epochs: 3
|
| 338 |
|
| 339 |
+
pattern_memory:
|
| 340 |
+
enabled: true
|
| 341 |
+
max_patterns: 10000
|
| 342 |
+
similarity_threshold: 0.75
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 343 |
```
|
| 344 |
|
| 345 |
---
|
|
|
|
| 348 |
|
| 349 |
```
|
| 350 |
stack-2.9/
|
| 351 |
+
├── stack_cli/ # CLI interface & agent
|
| 352 |
+
│ ├── cli.py # Main entry point
|
| 353 |
+
│ ├── agent.py # AI agent with tools
|
| 354 |
+
│ └── context.py # Context management
|
| 355 |
│
|
| 356 |
+
├── stack_2_9_eval/ # Evaluation framework
|
| 357 |
+
│ ├── model_client.py # Unified model API
|
| 358 |
+
│ └── benchmarks/ # Benchmark implementations
|
| 359 |
│
|
| 360 |
+
├── stack_2_9_training/ # Training scripts
|
| 361 |
+
│ ├── train_lora.py # LoRA training
|
| 362 |
+
│ ├── merge_adapter.py # Merge LoRA into base
|
| 363 |
+
│ └── prepare_data.py # Data preparation
|
| 364 |
│
|
| 365 |
+
├── stack_2_9_deploy/ # Deployment configs
|
| 366 |
+
│ ├── docker-compose.yml
|
| 367 |
+
│ └── nginx.conf
|
| 368 |
│
|
| 369 |
+
├── scripts/ # Utility scripts
|
| 370 |
+
│ ├── extract_patterns_from_git.py
|
| 371 |
+
│ ├── merge_lora_adapters.py
|
| 372 |
+
│ └── ...
|
| 373 |
+
│
|
| 374 |
+
├── docs/ # Documentation
|
| 375 |
+
│ ├── pattern-moat.md # Pattern memory evolution
|
| 376 |
+
│ └── ...
|
| 377 |
+
│
|
| 378 |
+
├── k8s/ # Kubernetes configs
|
| 379 |
+
│ ├── deployment.yaml
|
| 380 |
+
│ ├── service.yaml
|
| 381 |
+
│ └── secret.yaml
|
| 382 |
+
│
|
| 383 |
+
├── TOOLS.md # Complete tool reference (46 tools)
|
| 384 |
+
├── README.md # This file
|
| 385 |
+
├── requirements.txt # Python dependencies
|
| 386 |
+
├── stack.yaml # Config (create your own)
|
| 387 |
+
└── colab_train_stack29.ipynb # Quick training notebook
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
```
|
| 389 |
|
| 390 |
---
|
| 391 |
|
| 392 |
+
## 🤝 Contributing
|
| 393 |
|
| 394 |
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting PRs.
|
|
|
|
|
|
|
|
|
|
| 395 |
|
| 396 |
+
1. Fork the repository
|
| 397 |
+
2. Create feature branch: `git checkout -b feature/amazing-feature`
|
| 398 |
+
3. Commit changes: `git commit -m 'Add amazing feature'`
|
| 399 |
+
4. Push to branch: `git push origin feature/amazing-feature`
|
| 400 |
+
5. Open Pull Request
|
| 401 |
|
| 402 |
---
|
| 403 |
|
| 404 |
+
## 📄 License
|
| 405 |
|
| 406 |
+
Licensed under the **MIT License**. See [LICENSE](LICENSE) for full text.
|
|
|
|
|
|
|
|
|
|
| 407 |
|
| 408 |
+
### Dependencies
|
| 409 |
|
| 410 |
+
- Base model: Qwen2.5-Coder-32B (Apache 2.0)
|
| 411 |
+
- Training code: HuggingFace Transformers, PEFT, bitsandbytes (Apache 2.0 / BSD)
|
| 412 |
+
- Your modifications: MIT
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 413 |
|
| 414 |
---
|
| 415 |
|
| 416 |
+
## 🙏 Acknowledgments
|
| 417 |
|
| 418 |
+
- [Qwen](https://github.com/Qwen) for Qwen2.5-Coder base model
|
| 419 |
+
- [Hugging Face](https://huggingface.co/) for transformers & PEFT
|
| 420 |
+
- [Ollama](https://ollama.ai/) for local inference platform
|
| 421 |
+
- [Together AI](https://together.ai/) for cloud inference & fine-tuning
|
| 422 |
|
| 423 |
---
|
| 424 |
|
| 425 |
+
## 📚 Documentation
|
| 426 |
|
| 427 |
+
- [API Reference](docs/reference/API.md)
|
| 428 |
+
- [Architecture](docs/reference/ARCHITECTURE.md)
|
| 429 |
+
- [Setup Guide](docs/guides/SETUP.md)
|
| 430 |
+
- [Evaluation Plan](stack-2.9-eval/HUMAN_EVAL_PLAN.md)
|
| 431 |
+
- [Tool Reference](TOOLS.md)
|
| 432 |
+
- [Pattern Memory Evolution](docs/pattern-moat.md)
|
| 433 |
|
| 434 |
---
|
| 435 |
|
| 436 |
<p align="center">
|
| 437 |
Built with ❤️ for developers who want an AI that grows with them
|
| 438 |
</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TOOLS.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TOOLS.md - Local Notes
|
| 2 |
+
|
| 3 |
+
Skills define _how_ tools work. This file is for _your_ specifics — the stuff that's unique to your setup.
|
| 4 |
+
|
| 5 |
+
## What Goes Here
|
| 6 |
+
|
| 7 |
+
Things like:
|
| 8 |
+
|
| 9 |
+
- Camera names and locations
|
| 10 |
+
- SSH hosts and aliases
|
| 11 |
+
- Preferred voices for TTS
|
| 12 |
+
- Speaker/room names
|
| 13 |
+
- Device nicknames
|
| 14 |
+
- Anything environment-specific
|
| 15 |
+
|
| 16 |
+
## Examples
|
| 17 |
+
|
| 18 |
+
```markdown
|
| 19 |
+
### Cameras
|
| 20 |
+
|
| 21 |
+
- living-room → Main area, 180° wide angle
|
| 22 |
+
- front-door → Entrance, motion-triggered
|
| 23 |
+
|
| 24 |
+
### SSH
|
| 25 |
+
|
| 26 |
+
- home-server → 192.168.1.100, user: admin
|
| 27 |
+
|
| 28 |
+
### TTS
|
| 29 |
+
|
| 30 |
+
- Preferred voice: "Nova" (warm, slightly British)
|
| 31 |
+
- Default speaker: Kitchen HomePod
|
| 32 |
+
```
|
| 33 |
+
|
| 34 |
+
## Why Separate?
|
| 35 |
+
|
| 36 |
+
Skills are shared. Your setup is yours. Keeping them apart means you can update skills without losing your notes, and share skills without leaking your infrastructure.
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
Add whatever helps you do your job. This is your cheat sheet.
|
docs/pattern-moat.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pattern Memory Evolution
|
| 2 |
+
|
| 3 |
+
The Pattern Memory Moat is a system for capturing, storing, and sharing code patterns across teams. It transforms individual learning into collective intelligence.
|
| 4 |
+
|
| 5 |
+
## Table of Contents
|
| 6 |
+
|
| 7 |
+
1. [Auto-Extraction](#auto-extraction)
|
| 8 |
+
2. [Team Sync](#team-sync)
|
| 9 |
+
3. [Weight Fusion](#weight-fusion)
|
| 10 |
+
4. [API Reference](#api-reference)
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## Auto-Extraction
|
| 15 |
+
|
| 16 |
+
Extract patterns automatically from your Git history. The system analyzes commit messages, identifies bug fixes and features, and stores the before/after code changes.
|
| 17 |
+
|
| 18 |
+
### How It Works
|
| 19 |
+
|
| 20 |
+
The `extract_patterns_from_git.py` script:
|
| 21 |
+
|
| 22 |
+
1. **Scans Git History**: Reads through commit messages and diffs
|
| 23 |
+
2. **Identifies Patterns**: Uses keywords to classify commits as bug fixes or features
|
| 24 |
+
3. **Extracts Context**: Captures before/after code with metadata
|
| 25 |
+
4. **Stores in JSONL**: Outputs structured data suitable for training
|
| 26 |
+
|
| 27 |
+
### Usage
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
# Extract patterns from all commits
|
| 31 |
+
python scripts/extract_patterns_from_git.py \
|
| 32 |
+
--repo-path /path/to/repo \
|
| 33 |
+
--output patterns.jsonl
|
| 34 |
+
|
| 35 |
+
# Only recent commits
|
| 36 |
+
python scripts/extract_patterns_from_git.py \
|
| 37 |
+
--repo-path /path/to/repo \
|
| 38 |
+
--output patterns.jsonl \
|
| 39 |
+
--since-date "2024-01-01"
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
### Output Format
|
| 43 |
+
|
| 44 |
+
Each line in the JSONL output:
|
| 45 |
+
|
| 46 |
+
```json
|
| 47 |
+
{
|
| 48 |
+
"pattern_id": "a1b2c3d4e5f6g7h8",
|
| 49 |
+
"problem_type": "bug_fix",
|
| 50 |
+
"before_code": "def buggy_function():\n return None + 1",
|
| 51 |
+
"after_code": "def fixed_function():\n return 1",
|
| 52 |
+
"commit_msg": "fix: handle None case in function",
|
| 53 |
+
"author": "developer@example.com",
|
| 54 |
+
"date": "2024-03-15 10:30:00",
|
| 55 |
+
"confidence": 0.85
|
| 56 |
+
}
|
| 57 |
+
```
|
| 58 |
+
|
| 59 |
+
### Problem Types
|
| 60 |
+
|
| 61 |
+
- `bug_fix`: Commits that resolve issues (keywords: fix, bug, hotfix, patch, resolve)
|
| 62 |
+
- `feature_addition`: Commits that add new functionality (keywords: feat, add, implement, enhance)
|
| 63 |
+
- `unknown`: Other commits (typically skipped)
|
| 64 |
+
|
| 65 |
+
### Confidence Scoring
|
| 66 |
+
|
| 67 |
+
The confidence score (0.0-1.0) reflects pattern quality:
|
| 68 |
+
|
| 69 |
+
- Base: 0.5
|
| 70 |
+
- +0.2 for clear bug fix keywords
|
| 71 |
+
- +0.15 for clear feature keywords
|
| 72 |
+
- +0.15 for having both before and after code
|
| 73 |
+
- +0.1 for substantial changes (>100 chars)
|
| 74 |
+
- +0.1 for large changes (>500 chars)
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## Team Sync
|
| 79 |
+
|
| 80 |
+
Share and sync patterns across your team using a shared PostgreSQL database.
|
| 81 |
+
|
| 82 |
+
### PostgreSQL Schema
|
| 83 |
+
|
| 84 |
+
```sql
|
| 85 |
+
CREATE TABLE patterns (
|
| 86 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 87 |
+
problem_type VARCHAR(50) NOT NULL,
|
| 88 |
+
solution_hash VARCHAR(64) NOT NULL,
|
| 89 |
+
code_before TEXT NOT NULL,
|
| 90 |
+
code_after TEXT NOT NULL,
|
| 91 |
+
success_count INTEGER DEFAULT 0,
|
| 92 |
+
last_used TIMESTAMP,
|
| 93 |
+
created_by VARCHAR(255) NOT NULL,
|
| 94 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 95 |
+
|
| 96 |
+
-- Indexes
|
| 97 |
+
CONSTRAINT unique_solution UNIQUE (solution_hash),
|
| 98 |
+
INDEX idx_problem_type (problem_type),
|
| 99 |
+
INDEX idx_success_count (success_count DESC)
|
| 100 |
+
);
|
| 101 |
+
|
| 102 |
+
CREATE TABLE pattern_feedback (
|
| 103 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 104 |
+
pattern_id UUID REFERENCES patterns(id),
|
| 105 |
+
user_id VARCHAR(255) NOT NULL,
|
| 106 |
+
helpful BOOLEAN NOT NULL,
|
| 107 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 108 |
+
);
|
| 109 |
+
|
| 110 |
+
CREATE TABLE adapter_versions (
|
| 111 |
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
| 112 |
+
version_name VARCHAR(100) NOT NULL,
|
| 113 |
+
adapter_path VARCHAR(500) NOT NULL,
|
| 114 |
+
created_by VARCHAR(255) NOT NULL,
|
| 115 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 116 |
+
is_active BOOLEAN DEFAULT FALSE
|
| 117 |
+
);
|
| 118 |
+
```
|
| 119 |
+
|
| 120 |
+
### FastAPI Endpoints
|
| 121 |
+
|
| 122 |
+
#### GET /patterns
|
| 123 |
+
|
| 124 |
+
List patterns with filtering and pagination.
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
curl -H "X-API-Key: your-api-key" \
|
| 128 |
+
"http://localhost:8000/patterns?problem_type=bug_fix&limit=20"
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
Response:
|
| 132 |
+
```json
|
| 133 |
+
{
|
| 134 |
+
"patterns": [...],
|
| 135 |
+
"total": 150,
|
| 136 |
+
"page": 1,
|
| 137 |
+
"per_page": 20
|
| 138 |
+
}
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
#### POST /patterns
|
| 142 |
+
|
| 143 |
+
Add a new pattern.
|
| 144 |
+
|
| 145 |
+
```bash
|
| 146 |
+
curl -X POST -H "X-API-Key: your-api-key" \
|
| 147 |
+
-H "Content-Type: application/json" \
|
| 148 |
+
-d '{"problem_type": "bug_fix", "code_before": "...", "code_after": "..."}' \
|
| 149 |
+
"http://localhost:8000/patterns"
|
| 150 |
+
```
|
| 151 |
+
|
| 152 |
+
#### POST /patterns/{id}/feedback
|
| 153 |
+
|
| 154 |
+
Submit feedback on a pattern.
|
| 155 |
+
|
| 156 |
+
```bash
|
| 157 |
+
curl -X POST -H "X-API-Key: your-api-key" \
|
| 158 |
+
-H "Content-Type: application/json" \
|
| 159 |
+
-d '{"helpful": true}' \
|
| 160 |
+
"http://localhost:8000/patterns/123e4567-e89b-12d3-a456-426614174000/feedback"
|
| 161 |
+
```
|
| 162 |
+
|
| 163 |
+
### Authentication
|
| 164 |
+
|
| 165 |
+
API key authentication via `X-API-Key` header:
|
| 166 |
+
|
| 167 |
+
```python
|
| 168 |
+
# Server-side middleware
|
| 169 |
+
async def verify_api_key(request: Request, call_next):
|
| 170 |
+
api_key = request.headers.get("X-API-Key")
|
| 171 |
+
if not api_key or api_key != settings.API_KEY:
|
| 172 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 173 |
+
return await call_next(request)
|
| 174 |
+
```
|
| 175 |
+
|
| 176 |
+
### Conflict Resolution
|
| 177 |
+
|
| 178 |
+
When multiple team members contribute similar patterns:
|
| 179 |
+
|
| 180 |
+
1. **Pattern Similarity Detection**: Hash-based deduplication
|
| 181 |
+
2. **Merge Strategy**: Patterns with similar `solution_hash` are merged
|
| 182 |
+
3. **Success Rate Tracking**: `success_count` increases with positive feedback
|
| 183 |
+
4. **Priority**: Patterns with higher `success_count` rank higher in queries
|
| 184 |
+
|
| 185 |
+
---
|
| 186 |
+
|
| 187 |
+
## Weight Fusion
|
| 188 |
+
|
| 189 |
+
Combine LoRA adapters from multiple users using weighted averaging based on success rates.
|
| 190 |
+
|
| 191 |
+
### Algorithm
|
| 192 |
+
|
| 193 |
+
```
|
| 194 |
+
merged_weight = Σ(adapter_i.weight * adapter_i.success_rate) / Σ(success_rate)
|
| 195 |
+
```
|
| 196 |
+
|
| 197 |
+
This ensures adapters that have shown better results contribute more to the final merged adapter.
|
| 198 |
+
|
| 199 |
+
### Merge Script Usage
|
| 200 |
+
|
| 201 |
+
```bash
|
| 202 |
+
# Basic merge with manual weights
|
| 203 |
+
python scripts/merge_lora_adapters.py \
|
| 204 |
+
--adapters user1_adapter.safetensors user2_adapter.safetensors \
|
| 205 |
+
--weights 0.6 0.4 \
|
| 206 |
+
--output merged_adapter.safetensors
|
| 207 |
+
|
| 208 |
+
# Merge using success rates (auto-computes proportional weights)
|
| 209 |
+
python scripts/merge_lora_adapters.py \
|
| 210 |
+
--adapters alice_adapter.safetensors bob_adapter.safetensors \
|
| 211 |
+
--success-rates 0.85 0.65 \
|
| 212 |
+
--output team_adapter.safetensors
|
| 213 |
+
|
| 214 |
+
# Equal weights (default)
|
| 215 |
+
python scripts/merge_lora_adapters.py \
|
| 216 |
+
--adapters adapter1.safetensors adapter2.safetensors \
|
| 217 |
+
--output merged.safetensors
|
| 218 |
+
```
|
| 219 |
+
|
| 220 |
+
### Versioning
|
| 221 |
+
|
| 222 |
+
Each merge creates a version record:
|
| 223 |
+
|
| 224 |
+
```json
|
| 225 |
+
{
|
| 226 |
+
"version_name": "v2.1-team-merge",
|
| 227 |
+
"adapter_path": "/adapters/merged_v2.1.safetensors",
|
| 228 |
+
"created_by": "alice@example.com",
|
| 229 |
+
"created_at": "2024-03-15T10:30:00Z",
|
| 230 |
+
"parent_versions": ["v2.0", "user-alice-v3", "user-bob-v2"]
|
| 231 |
+
}
|
| 232 |
+
```
|
| 233 |
+
|
| 234 |
+
### Rollback
|
| 235 |
+
|
| 236 |
+
To revert to a previous merged adapter:
|
| 237 |
+
|
| 238 |
+
```bash
|
| 239 |
+
# List available versions
|
| 240 |
+
ls -la adapters/versions/
|
| 241 |
+
|
| 242 |
+
# Restore previous version
|
| 243 |
+
cp adapters/versions/v2.0.safetensors adapters/merged.safetensors
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
Or via API:
|
| 247 |
+
|
| 248 |
+
```bash
|
| 249 |
+
curl -X POST -H "X-API-Key: your-api-key" \
|
| 250 |
+
-d '{"version_id": "123e4567-e89b-12d3-a456-426614174000"}' \
|
| 251 |
+
"http://localhost:8000/adapters/rollback"
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
---
|
| 255 |
+
|
| 256 |
+
## API Reference
|
| 257 |
+
|
| 258 |
+
### Patterns API
|
| 259 |
+
|
| 260 |
+
| Method | Endpoint | Description |
|
| 261 |
+
|--------|----------|-------------|
|
| 262 |
+
| GET | `/patterns` | List patterns |
|
| 263 |
+
| GET | `/patterns/{id}` | Get pattern by ID |
|
| 264 |
+
| POST | `/patterns` | Create pattern |
|
| 265 |
+
| POST | `/patterns/{id}/feedback` | Submit feedback |
|
| 266 |
+
| DELETE | `/patterns/{id}` | Delete pattern |
|
| 267 |
+
|
| 268 |
+
### Adapter API
|
| 269 |
+
|
| 270 |
+
| Method | Endpoint | Description |
|
| 271 |
+
|--------|----------|-------------|
|
| 272 |
+
| GET | `/adapters` | List adapter versions |
|
| 273 |
+
| POST | `/adapters/merge` | Merge multiple adapters |
|
| 274 |
+
| POST | `/adapters/{id}/activate` | Set as active adapter |
|
| 275 |
+
| POST | `/adapters/rollback` | Rollback to previous version |
|
| 276 |
+
|
| 277 |
+
### Health Check
|
| 278 |
+
|
| 279 |
+
```bash
|
| 280 |
+
curl "http://localhost:8000/health"
|
| 281 |
+
```
|
| 282 |
+
|
| 283 |
+
Response:
|
| 284 |
+
```json
|
| 285 |
+
{
|
| 286 |
+
"status": "healthy",
|
| 287 |
+
"version": "1.0.0",
|
| 288 |
+
"database": "connected"
|
| 289 |
+
}
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
---
|
| 293 |
+
|
| 294 |
+
## Example Workflow
|
| 295 |
+
|
| 296 |
+
### 1. Extract Patterns from Project
|
| 297 |
+
|
| 298 |
+
```bash
|
| 299 |
+
# Extract patterns from your codebase
|
| 300 |
+
python scripts/extract_patterns_from_git.py \
|
| 301 |
+
--repo-path ./my-project \
|
| 302 |
+
--output patterns.jsonl \
|
| 303 |
+
--since-date "2024-01-01"
|
| 304 |
+
```
|
| 305 |
+
|
| 306 |
+
### 2. Upload to Team Database
|
| 307 |
+
|
| 308 |
+
```python
|
| 309 |
+
import requests
|
| 310 |
+
|
| 311 |
+
with open('patterns.jsonl') as f:
|
| 312 |
+
for line in f:
|
| 313 |
+
pattern = json.loads(line)
|
| 314 |
+
requests.post(
|
| 315 |
+
"http://team-patterns.example.com/patterns",
|
| 316 |
+
headers={"X-API-Key": "your-key"},
|
| 317 |
+
json=pattern
|
| 318 |
+
)
|
| 319 |
+
```
|
| 320 |
+
|
| 321 |
+
### 3. Merge Team Patterns
|
| 322 |
+
|
| 323 |
+
```bash
|
| 324 |
+
# Merge adapters from team members
|
| 325 |
+
python scripts/merge_lora_adapters.py \
|
| 326 |
+
--adapters alice_adapter.safetensors bob_adapter.safetensors carol_adapter.safetensors \
|
| 327 |
+
--success-rates 0.90 0.75 0.85 \
|
| 328 |
+
--output team_merged.safetensors
|
| 329 |
+
```
|
| 330 |
+
|
| 331 |
+
### 4. Activate for Team Use
|
| 332 |
+
|
| 333 |
+
The merged adapter with the highest success rate becomes the new team baseline.
|
| 334 |
+
|
| 335 |
+
---
|
| 336 |
+
|
| 337 |
+
## Files Reference
|
| 338 |
+
|
| 339 |
+
| File | Description |
|
| 340 |
+
|------|-------------|
|
| 341 |
+
| `scripts/extract_patterns_from_git.py` | Git history pattern extractor |
|
| 342 |
+
| `scripts/merge_lora_adapters.py` | LoRA adapter merger |
|
| 343 |
+
| `docs/pattern-moat.md` | This documentation |
|
k8s/deployment.yaml
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# Stack 2.9 Kubernetes Deployment
|
| 3 |
+
# =============================================================================
|
| 4 |
+
# Deploys Stack 2.9 (Qwen2.5-Coder LoRA training or inference) on a
|
| 5 |
+
# GPU-enabled Kubernetes cluster with nvidia.com/gpu nodes.
|
| 6 |
+
#
|
| 7 |
+
# Usage:
|
| 8 |
+
# kubectl apply -f k8s/namespace.yaml
|
| 9 |
+
# kubectl apply -f k8s/secret.yaml # First, edit with your tokens
|
| 10 |
+
# kubectl apply -f k8s/configmap.yaml
|
| 11 |
+
# kubectl apply -f k8s/pvc.yaml
|
| 12 |
+
# kubectl apply -f k8s/deployment.yaml
|
| 13 |
+
# kubectl apply -f k8s/service.yaml # For inference mode
|
| 14 |
+
#
|
| 15 |
+
# Requirements:
|
| 16 |
+
# - Kubernetes >= 1.26
|
| 17 |
+
# - NVIDIA GPU operator installed: https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/
|
| 18 |
+
# - A StorageClass for PVCs (e.g., standard, hostPath for single-node)
|
| 19 |
+
#
|
| 20 |
+
# =============================================================================
|
| 21 |
+
|
| 22 |
+
apiVersion: v1
|
| 23 |
+
kind: Namespace
|
| 24 |
+
metadata:
|
| 25 |
+
name: stack-29
|
| 26 |
+
labels:
|
| 27 |
+
app.kubernetes.io/name: stack-29
|
| 28 |
+
app.kubernetes.io/part-of: ai-voice-clone
|
| 29 |
+
---
|
| 30 |
+
apiVersion: v1
|
| 31 |
+
kind: ConfigMap
|
| 32 |
+
metadata:
|
| 33 |
+
name: stack-29-config
|
| 34 |
+
namespace: stack-29
|
| 35 |
+
data:
|
| 36 |
+
# Model configuration - override with your values
|
| 37 |
+
MODEL_NAME: "Qwen/Qwen2.5-Coder-7B" # Use 7B for single GPU, 32B for multi-GPU A100
|
| 38 |
+
MAX_SEQ_LENGTH: "8192" # Reduce for less VRAM, increase for A100 80GB
|
| 39 |
+
LOAD_IN_4BIT: "true"
|
| 40 |
+
|
| 41 |
+
# LoRA configuration
|
| 42 |
+
LORA_RANK: "64"
|
| 43 |
+
LORA_ALPHA: "128"
|
| 44 |
+
LORA_DROPOUT: "0.05"
|
| 45 |
+
TARGET_MODULES: "q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj"
|
| 46 |
+
|
| 47 |
+
# Training configuration
|
| 48 |
+
LEARNING_RATE: "1.0e-4"
|
| 49 |
+
NUM_TRAIN_EPOCHS: "3"
|
| 50 |
+
WARMUP_STEPS: "100"
|
| 51 |
+
GRADIENT_ACCUMULATION_STEPS: "16"
|
| 52 |
+
PER_DEVICE_TRAIN_BATCH_SIZE: "1"
|
| 53 |
+
SAVE_STEPS: "500"
|
| 54 |
+
LOGGING_STEPS: "10"
|
| 55 |
+
|
| 56 |
+
# Application mode: "train" or "inference"
|
| 57 |
+
APP_MODE: "train"
|
| 58 |
+
|
| 59 |
+
# HF Cache directory inside container
|
| 60 |
+
HF_HOME: "/data/hf_cache"
|
| 61 |
+
|
| 62 |
+
---
|
| 63 |
+
# Sensitive tokens - stored as a Kubernetes Secret (base64-encoded)
|
| 64 |
+
# Create with: kubectl create secret generic stack-29-secrets \
|
| 65 |
+
# --from-literal=HF_TOKEN=your_token \
|
| 66 |
+
# --from-literal=EXTRA_TOKENS=any_other_tokens \
|
| 67 |
+
# --namespace=stack-29
|
| 68 |
+
apiVersion: v1
|
| 69 |
+
kind: Secret
|
| 70 |
+
metadata:
|
| 71 |
+
name: stack-29-secrets
|
| 72 |
+
namespace: stack-29
|
| 73 |
+
type: Opaque
|
| 74 |
+
stringData:
|
| 75 |
+
HF_TOKEN: "REPLACE_WITH_YOUR_HF_TOKEN" # Required for Qwen models
|
| 76 |
+
# Add other secrets as needed:
|
| 77 |
+
# OPENAI_API_KEY: "sk-..."
|
| 78 |
+
# ANTHROPIC_API_KEY: "sk-ant-..."
|
| 79 |
+
---
|
| 80 |
+
# PersistentVolumeClaim for model weights and outputs
|
| 81 |
+
apiVersion: v1
|
| 82 |
+
kind: PersistentVolumeClaim
|
| 83 |
+
metadata:
|
| 84 |
+
name: stack-29-models
|
| 85 |
+
namespace: stack-29
|
| 86 |
+
spec:
|
| 87 |
+
accessModes:
|
| 88 |
+
- ReadWriteMany # ROX for training data, RWX for outputs
|
| 89 |
+
resources:
|
| 90 |
+
requests:
|
| 91 |
+
storage: 100Gi # Adjust based on model size
|
| 92 |
+
#storageClassName: standard # Use 'local-path' for k3s, 'standard' for GKE
|
| 93 |
+
selector:
|
| 94 |
+
matchLabels:
|
| 95 |
+
type: models-cache
|
| 96 |
+
---
|
| 97 |
+
# PersistentVolumeClaim for training outputs
|
| 98 |
+
apiVersion: v1
|
| 99 |
+
kind: PersistentVolumeClaim
|
| 100 |
+
metadata:
|
| 101 |
+
name: stack-29-outputs
|
| 102 |
+
namespace: stack-29
|
| 103 |
+
spec:
|
| 104 |
+
accessModes:
|
| 105 |
+
- ReadWriteOnce
|
| 106 |
+
resources:
|
| 107 |
+
requests:
|
| 108 |
+
storage: 50Gi
|
| 109 |
+
---
|
| 110 |
+
apiVersion: apps/v1
|
| 111 |
+
kind: Deployment
|
| 112 |
+
metadata:
|
| 113 |
+
name: stack-29
|
| 114 |
+
namespace: stack-29
|
| 115 |
+
labels:
|
| 116 |
+
app: stack-29
|
| 117 |
+
version: v1
|
| 118 |
+
spec:
|
| 119 |
+
replicas: 1
|
| 120 |
+
|
| 121 |
+
# Strategy: replace pod on config change (rolling not ideal for training)
|
| 122 |
+
strategy:
|
| 123 |
+
type: Recreate
|
| 124 |
+
|
| 125 |
+
selector:
|
| 126 |
+
matchLabels:
|
| 127 |
+
app: stack-29
|
| 128 |
+
|
| 129 |
+
template:
|
| 130 |
+
metadata:
|
| 131 |
+
labels:
|
| 132 |
+
app: stack-29
|
| 133 |
+
version: v1
|
| 134 |
+
annotations:
|
| 135 |
+
# Signal to prometheus/scheduler that this needs a GPU node
|
| 136 |
+
nvidia.com/gpu.count: "1"
|
| 137 |
+
nvidia.com/gpu.product: "NVIDIA-A100-80GB" # Adjust per your node
|
| 138 |
+
spec:
|
| 139 |
+
# Schedule on GPU node
|
| 140 |
+
nodeSelector:
|
| 141 |
+
# Customize these to match your GPU node labels
|
| 142 |
+
# Example for nodes with A100:
|
| 143 |
+
# nvidia.com/gpu.product: "NVIDIA-A100-80GB"
|
| 144 |
+
# Example for any GPU:
|
| 145 |
+
nvidia.com/gpu.present: "true"
|
| 146 |
+
|
| 147 |
+
tolerations:
|
| 148 |
+
# Allow scheduling on GPU nodes (they may have taints)
|
| 149 |
+
- key: "nvidia.com/gpu"
|
| 150 |
+
operator: "Exists"
|
| 151 |
+
effect: "NoSchedule"
|
| 152 |
+
|
| 153 |
+
# Graceful shutdown
|
| 154 |
+
terminationGracePeriodSeconds: 120
|
| 155 |
+
|
| 156 |
+
containers:
|
| 157 |
+
- name: stack-29
|
| 158 |
+
# Use the project's Dockerfile or a pre-built image
|
| 159 |
+
# Replace with your image registry
|
| 160 |
+
image: ghcr.io/walidsobhie-code/ai-voice-clone:latest
|
| 161 |
+
imagePullPolicy: Always
|
| 162 |
+
|
| 163 |
+
env:
|
| 164 |
+
# Import secrets from Kubernetes Secret
|
| 165 |
+
- name: HF_TOKEN
|
| 166 |
+
valueFrom:
|
| 167 |
+
secretKeyRef:
|
| 168 |
+
name: stack-29-secrets
|
| 169 |
+
key: HF_TOKEN
|
| 170 |
+
optional: false
|
| 171 |
+
|
| 172 |
+
# Import config from ConfigMap
|
| 173 |
+
- name: MODEL_NAME
|
| 174 |
+
valueFrom:
|
| 175 |
+
configMapKeyRef:
|
| 176 |
+
name: stack-29-config
|
| 177 |
+
key: MODEL_NAME
|
| 178 |
+
- name: MAX_SEQ_LENGTH
|
| 179 |
+
valueFrom:
|
| 180 |
+
configMapKeyRef:
|
| 181 |
+
name: stack-29-config
|
| 182 |
+
key: MAX_SEQ_LENGTH
|
| 183 |
+
- name: LOAD_IN_4BIT
|
| 184 |
+
valueFrom:
|
| 185 |
+
configMapKeyRef:
|
| 186 |
+
name: stack-29-config
|
| 187 |
+
key: LOAD_IN_4BIT
|
| 188 |
+
- name: LORA_RANK
|
| 189 |
+
valueFrom:
|
| 190 |
+
configMapKeyRef:
|
| 191 |
+
name: stack-29-config
|
| 192 |
+
key: LORA_RANK
|
| 193 |
+
- name: LORA_ALPHA
|
| 194 |
+
valueFrom:
|
| 195 |
+
configMapKeyRef:
|
| 196 |
+
name: stack-29-config
|
| 197 |
+
key: LORA_ALPHA
|
| 198 |
+
- name: APP_MODE
|
| 199 |
+
valueFrom:
|
| 200 |
+
configMapKeyRef:
|
| 201 |
+
name: stack-29-config
|
| 202 |
+
key: APP_MODE
|
| 203 |
+
- name: HF_HOME
|
| 204 |
+
valueFrom:
|
| 205 |
+
configMapKeyRef:
|
| 206 |
+
name: stack-29-config
|
| 207 |
+
key: HF_HOME
|
| 208 |
+
|
| 209 |
+
# Performance / memory tuning
|
| 210 |
+
- name: PYTORCH_CUDA_ALLOC_CONF
|
| 211 |
+
value: "max_split_size_mb=512,garbage_collection_threshold=0.8"
|
| 212 |
+
- name: CUDA_VISIBLE_DEVICES
|
| 213 |
+
value: "0"
|
| 214 |
+
- name: PYTHONUNBUFFERED
|
| 215 |
+
value: "1"
|
| 216 |
+
|
| 217 |
+
# Training entry point
|
| 218 |
+
# Override with command for inference mode
|
| 219 |
+
command:
|
| 220 |
+
- python
|
| 221 |
+
- -m
|
| 222 |
+
- stack_2_9_training.train_lora
|
| 223 |
+
- --config
|
| 224 |
+
- /config/train_config.yaml
|
| 225 |
+
|
| 226 |
+
# Inference mode: uncomment this command instead
|
| 227 |
+
# command:
|
| 228 |
+
# - python
|
| 229 |
+
# - -m
|
| 230 |
+
# - uvicorn
|
| 231 |
+
# - stack.serve:app
|
| 232 |
+
# - --host
|
| 233 |
+
# - "0.0.0.0"
|
| 234 |
+
# - --port
|
| 235 |
+
# - "7860"
|
| 236 |
+
|
| 237 |
+
ports:
|
| 238 |
+
- name: http
|
| 239 |
+
containerPort: 7860
|
| 240 |
+
protocol: TCP
|
| 241 |
+
|
| 242 |
+
resources:
|
| 243 |
+
limits:
|
| 244 |
+
# GPU resources
|
| 245 |
+
nvidia.com/gpu: "1" # Request 1 GPU
|
| 246 |
+
memory: "64Gi"
|
| 247 |
+
cpu: "8"
|
| 248 |
+
requests:
|
| 249 |
+
nvidia.com/gpu: "1"
|
| 250 |
+
memory: "32Gi"
|
| 251 |
+
cpu: "4"
|
| 252 |
+
|
| 253 |
+
volumeMounts:
|
| 254 |
+
# Mount config from ConfigMap
|
| 255 |
+
- name: config
|
| 256 |
+
mountPath: /config
|
| 257 |
+
readOnly: true
|
| 258 |
+
# Mount PVC for model cache
|
| 259 |
+
- name: models-cache
|
| 260 |
+
mountPath: /data
|
| 261 |
+
# Mount PVC for outputs
|
| 262 |
+
- name: outputs
|
| 263 |
+
mountPath: /outputs
|
| 264 |
+
|
| 265 |
+
# Liveness/readiness probes (for inference mode)
|
| 266 |
+
# Disabled for training as it runs to completion
|
| 267 |
+
# livenessProbe:
|
| 268 |
+
# httpGet:
|
| 269 |
+
# path: /health
|
| 270 |
+
# port: 7860
|
| 271 |
+
# initialDelaySeconds: 60
|
| 272 |
+
# periodSeconds: 30
|
| 273 |
+
# readinessProbe:
|
| 274 |
+
# httpGet:
|
| 275 |
+
# path: /health
|
| 276 |
+
# port: 7860
|
| 277 |
+
# initialDelaySeconds: 30
|
| 278 |
+
# periodSeconds: 10
|
| 279 |
+
|
| 280 |
+
envFrom:
|
| 281 |
+
- configMapRef:
|
| 282 |
+
name: stack-29-config
|
| 283 |
+
|
| 284 |
+
volumes:
|
| 285 |
+
- name: config
|
| 286 |
+
configMap:
|
| 287 |
+
name: stack-29-config
|
| 288 |
+
- name: models-cache
|
| 289 |
+
persistentVolumeClaim:
|
| 290 |
+
claimName: stack-29-models
|
| 291 |
+
- name: outputs
|
| 292 |
+
persistentVolumeClaim:
|
| 293 |
+
claimName: stack-29-outputs
|
| 294 |
+
|
| 295 |
+
---
|
| 296 |
+
# Optional: HorizontalPodAutoscaler for inference mode
|
| 297 |
+
# Uncomment when running inference with multiple replicas
|
| 298 |
+
# apiVersion: autoscaling/v2
|
| 299 |
+
# kind: HorizontalPodAutoscaler
|
| 300 |
+
# metadata:
|
| 301 |
+
# name: stack-29-hpa
|
| 302 |
+
# namespace: stack-29
|
| 303 |
+
# spec:
|
| 304 |
+
# scaleTargetRef:
|
| 305 |
+
# apiVersion: apps/v1
|
| 306 |
+
# kind: Deployment
|
| 307 |
+
# name: stack-29
|
| 308 |
+
# minReplicas: 1
|
| 309 |
+
# maxReplicas: 3
|
| 310 |
+
# metrics:
|
| 311 |
+
# - type: Resource
|
| 312 |
+
# resource:
|
| 313 |
+
# name: nvidia.com/gpu
|
| 314 |
+
# target:
|
| 315 |
+
# type: Utilization
|
| 316 |
+
# averageUtilization: 80
|
| 317 |
+
# - type: Resource
|
| 318 |
+
# resource:
|
| 319 |
+
# name: cpu
|
| 320 |
+
# target:
|
| 321 |
+
# type: Utilization
|
| 322 |
+
# averageUtilization: 70
|
k8s/pvc.yaml
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# Stack 2.9 Kubernetes ConfigMap
|
| 3 |
+
# =============================================================================
|
| 4 |
+
# Contains non-sensitive configuration for Stack 2.9 training/inference.
|
| 5 |
+
# All values here can be viewed with: kubectl get configmap -n stack-29
|
| 6 |
+
#
|
| 7 |
+
# For secrets (tokens, API keys), use the Secret type (see secret.yaml).
|
| 8 |
+
#
|
| 9 |
+
# Usage:
|
| 10 |
+
# kubectl apply -f k8s/configmap.yaml --namespace=stack-29
|
| 11 |
+
#
|
| 12 |
+
# =============================================================================
|
| 13 |
+
|
| 14 |
+
apiVersion: v1
|
| 15 |
+
kind: ConfigMap
|
| 16 |
+
metadata:
|
| 17 |
+
name: stack-29-config
|
| 18 |
+
namespace: stack-29
|
| 19 |
+
labels:
|
| 20 |
+
app.kubernetes.io/name: stack-29
|
| 21 |
+
app.kubernetes.io/component: config
|
| 22 |
+
data:
|
| 23 |
+
# Application
|
| 24 |
+
APP_MODE: "train" # "train" or "inference"
|
| 25 |
+
|
| 26 |
+
# Model settings
|
| 27 |
+
MODEL_NAME: "Qwen/Qwen2.5-Coder-7B"
|
| 28 |
+
TRUST_REMOTE_CODE: "true"
|
| 29 |
+
MAX_SEQ_LENGTH: "8192"
|
| 30 |
+
|
| 31 |
+
# Quantization (4-bit for single GPU, 8-bit for better quality)
|
| 32 |
+
LOAD_IN_4BIT: "true"
|
| 33 |
+
LOAD_IN_8BIT: "false"
|
| 34 |
+
BNB_4BIT_COMPUTE_DTYPE: "bfloat16"
|
| 35 |
+
BNB_4BIT_QUANT_TYPE: "nf4"
|
| 36 |
+
BNB_4BIT_USE_DOUBLE_QUANT: "true"
|
| 37 |
+
|
| 38 |
+
# LoRA fine-tuning
|
| 39 |
+
LORA_RANK: "64"
|
| 40 |
+
LORA_ALPHA: "128"
|
| 41 |
+
LORA_DROPOUT: "0.05"
|
| 42 |
+
TARGET_MODULES: "q_proj,k_proj,v_proj,o_proj,gate_proj,up_proj,down_proj"
|
| 43 |
+
|
| 44 |
+
# Training hyperparameters
|
| 45 |
+
LEARNING_RATE: "1.0e-4"
|
| 46 |
+
NUM_TRAIN_EPOCHS: "3"
|
| 47 |
+
WARMUP_STEPS: "100"
|
| 48 |
+
WEIGHT_DECAY: "0.01"
|
| 49 |
+
GRADIENT_ACCUMULATION_STEPS: "16"
|
| 50 |
+
PER_DEVICE_TRAIN_BATCH_SIZE: "1"
|
| 51 |
+
PER_DEVICE_EVAL_BATCH_SIZE: "1"
|
| 52 |
+
GRADIENT_CHECKPOINTING: "true"
|
| 53 |
+
FP16: "false"
|
| 54 |
+
BF16: "true"
|
| 55 |
+
OPTIM: "paged_adamw_8bit"
|
| 56 |
+
|
| 57 |
+
# Checkpointing
|
| 58 |
+
SAVE_STEPS: "500"
|
| 59 |
+
EVAL_STEPS: "250"
|
| 60 |
+
LOGGING_STEPS: "10"
|
| 61 |
+
OUTPUT_DIR: "/outputs/adapters"
|
| 62 |
+
OVERWRITE_OUTPUT_DIR: "true"
|
| 63 |
+
|
| 64 |
+
# Data
|
| 65 |
+
DATASET_PATH: "/data/training-data/train.jsonl"
|
| 66 |
+
EVAL_PATH: "/data/training-data/eval.jsonl"
|
| 67 |
+
DATA_FORMAT: "chatml"
|
| 68 |
+
TRAIN_SPLIT: "0.9"
|
| 69 |
+
EVAL_SPLIT: "0.1"
|
| 70 |
+
|
| 71 |
+
# Paths
|
| 72 |
+
HF_HOME: "/data/hf_cache"
|
| 73 |
+
TRANSFORMERS_CACHE: "/data/hf_cache"
|
| 74 |
+
HF_DATASETS_CACHE: "/data/datasets_cache"
|
| 75 |
+
|
| 76 |
+
# Performance tuning
|
| 77 |
+
PYTORCH_CUDA_ALLOC_CONF: "max_split_size_mb=512"
|
| 78 |
+
CUDA_VISIBLE_DEVICES: "0"
|
| 79 |
+
|
| 80 |
+
# Misc
|
| 81 |
+
SEED: "42"
|
| 82 |
+
PUSH_TO_HUB: "false"
|
| 83 |
+
REMOVE_UNUSED_COLUMNS: "false"
|
| 84 |
+
|
| 85 |
+
# Inference server settings (used when APP_MODE=inference)
|
| 86 |
+
INFERENCE_PORT: "7860"
|
| 87 |
+
INFERENCE_HOST: "0.0.0.0"
|
k8s/secret.yaml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# Stack 2.9 Kubernetes Secret
|
| 3 |
+
# =============================================================================
|
| 4 |
+
# IMPORTANT: Never commit this file to version control with real tokens!
|
| 5 |
+
#
|
| 6 |
+
# This file is a TEMPLATE showing the structure.
|
| 7 |
+
# Replace "REPLACE_WITH_YOUR_..." values with actual tokens.
|
| 8 |
+
#
|
| 9 |
+
# SECURITY ALTERNATIVES (preferred over plain text):
|
| 10 |
+
# 1. Use external secrets management:
|
| 11 |
+
# - AWS Secrets Manager + External Secrets Operator
|
| 12 |
+
# - HashiCorp Vault
|
| 13 |
+
# - GCP Secret Manager
|
| 14 |
+
# 2. Use Kubernetes encrypted secrets at rest (etcdtls)
|
| 15 |
+
# 3. Use sealed secrets (Bitnami) for GitOps workflows
|
| 16 |
+
#
|
| 17 |
+
# To create secrets securely (without this file):
|
| 18 |
+
# kubectl create secret generic stack-29-secrets \
|
| 19 |
+
# --namespace=stack-29 \
|
| 20 |
+
# --from-literal=HF_TOKEN=hf_your_token_here \
|
| 21 |
+
# --from-literal=EXTRA_TOKENS=""
|
| 22 |
+
#
|
| 23 |
+
# To base64-encode a value manually:
|
| 24 |
+
# echo -n "your_token" | base64
|
| 25 |
+
#
|
| 26 |
+
# =============================================================================
|
| 27 |
+
|
| 28 |
+
apiVersion: v1
|
| 29 |
+
kind: Secret
|
| 30 |
+
metadata:
|
| 31 |
+
name: stack-29-secrets
|
| 32 |
+
namespace: stack-29
|
| 33 |
+
labels:
|
| 34 |
+
app.kubernetes.io/name: stack-29
|
| 35 |
+
app.kubernetes.io/component: secrets
|
| 36 |
+
type: Opaque
|
| 37 |
+
stringData:
|
| 38 |
+
# HuggingFace token - REQUIRED for Qwen models
|
| 39 |
+
# Get from: https://huggingface.co/settings/tokens
|
| 40 |
+
HF_TOKEN: "REPLACE_WITH_YOUR_HF_TOKEN"
|
| 41 |
+
|
| 42 |
+
# Optional: OpenAI API key (for evaluation/baselines)
|
| 43 |
+
# OPENAI_API_KEY: "sk-..."
|
| 44 |
+
|
| 45 |
+
# Optional: Anthropic API key
|
| 46 |
+
# ANTHROPIC_API_KEY: "sk-ant-..."
|
| 47 |
+
|
| 48 |
+
# Optional: Weights & Biases for experiment tracking
|
| 49 |
+
# WANDB_API_KEY: "your_wandb_key"
|
| 50 |
+
|
| 51 |
+
# Optional: Custom model hub tokens
|
| 52 |
+
# EXTRA_TOKENS: "token1,token2"
|
k8s/service.yaml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# =============================================================================
|
| 2 |
+
# Stack 2.9 Kubernetes Service
|
| 3 |
+
# =============================================================================
|
| 4 |
+
# Exposes the Stack 2.9 inference server (Gradio/FastAPI) via LoadBalancer.
|
| 5 |
+
# For training deployments this is typically not needed, but included for
|
| 6 |
+
# inference mode and for accessing tensorboard/monitoring.
|
| 7 |
+
#
|
| 8 |
+
# Usage:
|
| 9 |
+
# kubectl apply -f k8s/service.yaml --namespace=stack-29
|
| 10 |
+
#
|
| 11 |
+
# Note: Training deployments usually don't expose a service - logs are
|
| 12 |
+
# streamed via kubectl logs. This service is primarily for inference mode.
|
| 13 |
+
#
|
| 14 |
+
# =============================================================================
|
| 15 |
+
|
| 16 |
+
apiVersion: v1
|
| 17 |
+
kind: Service
|
| 18 |
+
metadata:
|
| 19 |
+
name: stack-29
|
| 20 |
+
namespace: stack-29
|
| 21 |
+
labels:
|
| 22 |
+
app: stack-29
|
| 23 |
+
annotations:
|
| 24 |
+
# For cloud providers, set the load balancer to target port 7860
|
| 25 |
+
# cloud.google.com/load-balancer-type: "External"
|
| 26 |
+
spec:
|
| 27 |
+
type: LoadBalancer # Use 'ClusterIP' for internal-only, 'NodePort' for simple exposure
|
| 28 |
+
ports:
|
| 29 |
+
- name: http
|
| 30 |
+
port: 7860 # External port (load balancer port)
|
| 31 |
+
targetPort: 7860 # Container port (defined in deployment)
|
| 32 |
+
protocol: TCP
|
| 33 |
+
|
| 34 |
+
# For inference, route to the correct pods
|
| 35 |
+
selector:
|
| 36 |
+
app: stack-29
|
| 37 |
+
|
| 38 |
+
# Preserve client IP for rate limiting / auth
|
| 39 |
+
# externalTrafficPolicy: Cluster
|
| 40 |
+
# sessionAffinity: ClientIP
|
| 41 |
+
|
| 42 |
+
---
|
| 43 |
+
# Additional service for training metrics (e.g., MLflow, TensorBoard)
|
| 44 |
+
# Uncomment if you add sidecar containers for monitoring
|
| 45 |
+
# apiVersion: v1
|
| 46 |
+
# kind: Service
|
| 47 |
+
# metadata:
|
| 48 |
+
# name: stack-29-metrics
|
| 49 |
+
# namespace: stack-29
|
| 50 |
+
# labels:
|
| 51 |
+
# app: stack-29
|
| 52 |
+
# component: metrics
|
| 53 |
+
# spec:
|
| 54 |
+
# type: ClusterIP
|
| 55 |
+
# ports:
|
| 56 |
+
# - name: tensorboard
|
| 57 |
+
# port: 6006
|
| 58 |
+
# targetPort: 6006
|
| 59 |
+
# protocol: TCP
|
| 60 |
+
# selector:
|
| 61 |
+
# app: stack-29
|
| 62 |
+
|
| 63 |
+
---
|
| 64 |
+
# Headless service for pod discovery (useful for distributed training)
|
| 65 |
+
# apiVersion: v1
|
| 66 |
+
# kind: Service
|
| 67 |
+
# metadata:
|
| 68 |
+
# name: stack-29-headless
|
| 69 |
+
# namespace: stack-29
|
| 70 |
+
# labels:
|
| 71 |
+
# app: stack-29
|
| 72 |
+
# spec:
|
| 73 |
+
# clusterIP: None # Headless - no ClusterIP
|
| 74 |
+
# ports:
|
| 75 |
+
# - name: ssh
|
| 76 |
+
# port: 22
|
| 77 |
+
# targetPort: 22
|
| 78 |
+
# protocol: TCP
|
| 79 |
+
# selector:
|
| 80 |
+
# app: stack-29
|
runpod_deploy.sh
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# =============================================================================
|
| 3 |
+
# runpod_deploy.sh - Deploy Stack 2.9 Training on RunPod
|
| 4 |
+
# =============================================================================
|
| 5 |
+
#
|
| 6 |
+
# USAGE:
|
| 7 |
+
# ./runpod_deploy.sh [--mode train|inference] [--config CONFIG_PATH] [--gpu GPU_TYPE]
|
| 8 |
+
#
|
| 9 |
+
# EXAMPLES:
|
| 10 |
+
# # Start training on an A100 80GB
|
| 11 |
+
# ./runpod_deploy.sh --mode train --gpu A100-80
|
| 12 |
+
#
|
| 13 |
+
# # Start inference server on a smaller GPU
|
| 14 |
+
# ./runpod_deploy.sh --mode inference --gpu A100-40
|
| 15 |
+
#
|
| 16 |
+
# # Use custom config
|
| 17 |
+
# ./runpod_deploy.sh --mode train --config ./my_config.yaml
|
| 18 |
+
#
|
| 19 |
+
# PREREQUISITES:
|
| 20 |
+
# - RunPod CLI installed: https://docs.runpod.io/cli/install
|
| 21 |
+
# - RunPod account with API key set: runpod config
|
| 22 |
+
# - HF_TOKEN set for gated models (Qwen)
|
| 23 |
+
#
|
| 24 |
+
# =============================================================================
|
| 25 |
+
|
| 26 |
+
set -euo pipefail
|
| 27 |
+
|
| 28 |
+
# ------------------------------ Defaults -------------------------------------
|
| 29 |
+
MODE="${MODE:-train}"
|
| 30 |
+
GPU_TYPE="${GPU_TYPE:-A100-80}"
|
| 31 |
+
CONFIG_PATH="${CONFIG_PATH:-./stack_2_9_training/train_config.yaml}"
|
| 32 |
+
HF_TOKEN="${HF_TOKEN:-}"
|
| 33 |
+
OUTPUT_DIR="${OUTPUT_DIR:-./stack-2.9}"
|
| 34 |
+
CONTAINER_DISK_SIZE="${CONTAINER_DISK_SIZE:-200}"
|
| 35 |
+
MIN_VRAM_GB="${MIN_VRAM_GB:-80}"
|
| 36 |
+
REPO_URL="${REPO_URL:-https://github.com/walidsobhie-code/ai-voice-clone.git}"
|
| 37 |
+
REPO_BRANCH="${REPO_BRANCH:-main}"
|
| 38 |
+
|
| 39 |
+
# ------------------------------ Helpers --------------------------------------
|
| 40 |
+
usage() {
|
| 41 |
+
grep "^#" "$0" | sed 's/^# //;s/^#//'
|
| 42 |
+
exit 1
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
| 46 |
+
error() { log "ERROR: $*" >&2; exit 1; }
|
| 47 |
+
|
| 48 |
+
require_cmd() {
|
| 49 |
+
command -v "$1" &>/dev/null || error "Required command not found: $1. Install it first."
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
# ------------------------------ Parse Args ----------------------------------
|
| 53 |
+
while [[ $# -gt 0 ]]; do
|
| 54 |
+
case $1 in
|
| 55 |
+
--mode) MODE="$2"; shift 2 ;;
|
| 56 |
+
--config) CONFIG_PATH="$2"; shift 2 ;;
|
| 57 |
+
--gpu) GPU_TYPE="$2"; shift 2 ;;
|
| 58 |
+
--help|-h) usage ;;
|
| 59 |
+
*) error "Unknown option: $1" ;;
|
| 60 |
+
esac
|
| 61 |
+
done
|
| 62 |
+
|
| 63 |
+
# Validate mode
|
| 64 |
+
if [[ "$MODE" != "train" && "$MODE" != "inference" ]]; then
|
| 65 |
+
error "Mode must be 'train' or 'inference', got: $MODE"
|
| 66 |
+
fi
|
| 67 |
+
|
| 68 |
+
# ------------------------------ Prerequisites --------------------------------
|
| 69 |
+
log "Checking prerequisites..."
|
| 70 |
+
require_cmd runpod
|
| 71 |
+
|
| 72 |
+
# Check HF_TOKEN
|
| 73 |
+
if [[ -z "$HF_TOKEN" ]]; then
|
| 74 |
+
log "WARNING: HF_TOKEN not set. Some models may fail to download."
|
| 75 |
+
log "Set it with: export HF_TOKEN=your_token_here"
|
| 76 |
+
fi
|
| 77 |
+
|
| 78 |
+
# --------------------------------- GPU Selection ----------------------------
|
| 79 |
+
# Map friendly names to RunPod GPU IDs
|
| 80 |
+
declare -A GPU_MAP
|
| 81 |
+
GPU_MAP["A100-80"]="NVIDIA-A100-80GB"
|
| 82 |
+
GPU_MAP["A100-40"]="NVIDIA-A100-40GB"
|
| 83 |
+
GPU_MAP["A6000"]="NVIDIA-RTX-A6000"
|
| 84 |
+
GPU_MAP["4090"]="NVIDIA-RTX-4090"
|
| 85 |
+
GPU_MAP["3090"]="NVIDIA-RTX-3090"
|
| 86 |
+
|
| 87 |
+
GPU_ID="${GPU_MAP[$GPU_TYPE]:-$GPU_TYPE}"
|
| 88 |
+
|
| 89 |
+
log "Selected GPU: $GPU_TYPE (RunPod ID: $GPU_ID)"
|
| 90 |
+
|
| 91 |
+
# ------------------------------ Detect GPU Availability ----------------------
|
| 92 |
+
log "Checking GPU availability on RunPod..."
|
| 93 |
+
|
| 94 |
+
# Find available pod templates with the requested GPU
|
| 95 |
+
AVAILABLE_GPUS=$(runpod list gpus 2>/dev/null | grep -c "$GPU_ID" || echo "0")
|
| 96 |
+
if [[ "$AVAILABLE_GPUS" == "0" ]]; then
|
| 97 |
+
log "WARNING: GPU $GPU_ID may not be available. Proceeding anyway..."
|
| 98 |
+
fi
|
| 99 |
+
|
| 100 |
+
# ------------------------------ Build Docker Command ------------------------
|
| 101 |
+
log "Building docker run command..."
|
| 102 |
+
|
| 103 |
+
# Base environment variables
|
| 104 |
+
ENV_VARS=(
|
| 105 |
+
"HF_TOKEN=${HF_TOKEN}"
|
| 106 |
+
"PYTORCH_CUDA_ALLOC_CONF=max_split_size_mb=512"
|
| 107 |
+
"TRANSFORMERS_CACHE=/data/hf_cache"
|
| 108 |
+
"HF_HOME=/data/hf_cache"
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
# Build env string
|
| 112 |
+
ENV_STRING=""
|
| 113 |
+
for var in "${ENV_VARS[@]}"; do
|
| 114 |
+
if [[ "$var" == "${var%=*}" ]]; then continue; fi # skip if no '='
|
| 115 |
+
KEY="${var%%=*}"
|
| 116 |
+
VAL="${var#*=}"
|
| 117 |
+
ENV_STRING+=" -e ${KEY}=${VAL}"
|
| 118 |
+
done
|
| 119 |
+
|
| 120 |
+
# Mount data volume for models and outputs
|
| 121 |
+
VOLUME_MOUNTS="-v /data:/data"
|
| 122 |
+
|
| 123 |
+
# Training command
|
| 124 |
+
if [[ "$MODE" == "train" ]]; then
|
| 125 |
+
CMD="python -m stack_2_9_training.train_lora \
|
| 126 |
+
--config ${CONFIG_PATH}"
|
| 127 |
+
CONTAINER_PORT=""
|
| 128 |
+
else
|
| 129 |
+
# Inference mode - start Gradio server
|
| 130 |
+
CMD="python -m uvicorn stack.serve:app \
|
| 131 |
+
--host 0.0.0.0 \
|
| 132 |
+
--port 7860"
|
| 133 |
+
CONTAINER_PORT="-p 7860:7860"
|
| 134 |
+
fi
|
| 135 |
+
|
| 136 |
+
# ------------------------------ Launch on RunPod -----------------------------
|
| 137 |
+
log "Launching RunPod instance..."
|
| 138 |
+
|
| 139 |
+
# Check if user wants interactive or one-liner
|
| 140 |
+
if [[ -t 0 ]]; then
|
| 141 |
+
log "Interactive mode - will print the docker command for manual run:"
|
| 142 |
+
echo ""
|
| 143 |
+
echo "runpod run --gpu ${GPU_ID} \\"
|
| 144 |
+
echo " --container-disk-size ${CONTAINER_DISK_SIZE} \\"
|
| 145 |
+
echo " ${ENV_STRING} \\"
|
| 146 |
+
echo " ${VOLUME_MOUNTS} \\"
|
| 147 |
+
echo " ${CONTAINER_PORT} \\"
|
| 148 |
+
echo " -- python /app/entrypoint.sh"
|
| 149 |
+
echo ""
|
| 150 |
+
echo "Recommended: Use runpod CLI with a template instead."
|
| 151 |
+
echo "See: https://docs.runpod.io/cli/templates"
|
| 152 |
+
else
|
| 153 |
+
# Non-interactive: use runpod run
|
| 154 |
+
runpod run \
|
| 155 |
+
--gpu "$GPU_ID" \
|
| 156 |
+
--container-disk-size "$CONTAINER_DISK_SIZE" \
|
| 157 |
+
docker \
|
| 158 |
+
bash -c "
|
| 159 |
+
set -e
|
| 160 |
+
echo '=== Starting Stack 2.9 Deployment ==='
|
| 161 |
+
echo 'Mode: $MODE'
|
| 162 |
+
echo 'GPU: $GPU_ID'
|
| 163 |
+
echo ''
|
| 164 |
+
echo '=== Installing dependencies ==='
|
| 165 |
+
pip install --no-cache-dir \
|
| 166 |
+
torch \
|
| 167 |
+
transformers \
|
| 168 |
+
peft \
|
| 169 |
+
accelerate \
|
| 170 |
+
bitsandbytes \
|
| 171 |
+
datasets \
|
| 172 |
+
trl \
|
| 173 |
+
pyyaml \
|
| 174 |
+
tqdm \
|
| 175 |
+
gradio \
|
| 176 |
+
fastapi \
|
| 177 |
+
uvicorn 2>&1 | tail -5
|
| 178 |
+
echo ''
|
| 179 |
+
echo '=== Cloning repository ==='
|
| 180 |
+
git clone --depth 1 -b $REPO_BRANCH $REPO_URL /app 2>/dev/null || echo 'Repo already present'
|
| 181 |
+
cd /app
|
| 182 |
+
echo ''
|
| 183 |
+
echo '=== Starting application ==='
|
| 184 |
+
$CMD
|
| 185 |
+
"
|
| 186 |
+
fi
|
| 187 |
+
|
| 188 |
+
# ------------------------------ Post-Launch --------------------------------
|
| 189 |
+
log "Done. To check your pod status:"
|
| 190 |
+
log " runpod ps"
|
| 191 |
+
log ""
|
| 192 |
+
log "To stream logs:"
|
| 193 |
+
log " runpod logs <pod-id>"
|
| 194 |
+
log ""
|
| 195 |
+
log "To SSH into the instance:"
|
| 196 |
+
log " runpod ssh <pod-id>"
|
| 197 |
+
|
| 198 |
+
# ------------------------------ Cleanup Hint ---------------------------------
|
| 199 |
+
log ""
|
| 200 |
+
log "To stop and remove the instance:"
|
| 201 |
+
log " runpod stop <pod-id> && runpod rm <pod-id>"
|
scripts/extract_patterns_from_git.py
CHANGED
|
@@ -1,457 +1,309 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
Extract
|
| 4 |
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
The extracted patterns can be used to enhance the Pattern Memory system.
|
| 8 |
|
| 9 |
Usage:
|
| 10 |
-
python extract_patterns_from_git.py --repo
|
| 11 |
-
python extract_patterns_from_git.py --repo . --output
|
| 12 |
"""
|
| 13 |
|
| 14 |
-
import os
|
| 15 |
-
import json
|
| 16 |
import argparse
|
|
|
|
|
|
|
|
|
|
| 17 |
import subprocess
|
| 18 |
-
|
| 19 |
-
from typing import Dict, List, Any, Optional, Set, Tuple
|
| 20 |
-
from collections import defaultdict, Counter
|
| 21 |
-
import re
|
| 22 |
from datetime import datetime
|
| 23 |
-
import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
| 25 |
-
|
| 26 |
-
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
self.patterns = []
|
| 32 |
-
self.stats = defaultdict(int)
|
| 33 |
-
|
| 34 |
-
def run_git_command(self, cmd: List[str]) -> str:
|
| 35 |
-
"""Run a git command and return output."""
|
| 36 |
-
try:
|
| 37 |
-
result = subprocess.run(
|
| 38 |
-
["git"] + cmd,
|
| 39 |
-
cwd=self.repo_path,
|
| 40 |
-
capture_output=True,
|
| 41 |
-
text=True,
|
| 42 |
-
timeout=30
|
| 43 |
-
)
|
| 44 |
-
return result.stdout.strip()
|
| 45 |
-
except subprocess.CalledProcessError as e:
|
| 46 |
-
print(f"Git command failed: {e}")
|
| 47 |
-
return ""
|
| 48 |
-
except subprocess.TimeoutExpired:
|
| 49 |
-
print(f"Git command timed out: {cmd}")
|
| 50 |
-
return ""
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
if limit:
|
| 64 |
-
cmd.append(f"-{limit}")
|
| 65 |
-
|
| 66 |
-
output = self.run_git_command(cmd)
|
| 67 |
commits = []
|
| 68 |
|
| 69 |
-
for line in
|
| 70 |
-
if not line
|
| 71 |
continue
|
| 72 |
-
parts = line.split('|'
|
| 73 |
-
if len(parts) =
|
| 74 |
-
commit_hash, author, date, subject, body = parts
|
| 75 |
commits.append({
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
"branch": branch
|
| 82 |
})
|
| 83 |
|
| 84 |
return commits
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
filename = parts[0].strip()
|
| 104 |
-
change_stats = parts[1].strip()
|
| 105 |
-
|
| 106 |
-
stats["files_changed"] += 1
|
| 107 |
-
|
| 108 |
-
# Extract file extension
|
| 109 |
-
if '.' in filename:
|
| 110 |
-
ext = filename.split('.')[-1].lower()
|
| 111 |
-
stats["file_types"][ext] += 1
|
| 112 |
-
|
| 113 |
-
# Count insertions/deletions
|
| 114 |
-
if '+' in change_stats:
|
| 115 |
-
try:
|
| 116 |
-
ins = int(change_stats.split('+')[0].strip().split()[0])
|
| 117 |
-
stats["insertions"] += ins
|
| 118 |
-
except:
|
| 119 |
-
pass
|
| 120 |
-
if '-' in change_stats:
|
| 121 |
-
try:
|
| 122 |
-
dels = change_stats.split('-')[0].strip().split()[-1]
|
| 123 |
-
stats["deletions"] += int(dels)
|
| 124 |
-
except:
|
| 125 |
-
pass
|
| 126 |
-
|
| 127 |
-
return stats
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
return self.run_git_command(["show", commit_hash])
|
| 132 |
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
# Keywords for classification
|
| 140 |
-
patterns = {
|
| 141 |
-
"bug_fix": ["fix", "bug", "issue", "error", "crash", "regression", "typo"],
|
| 142 |
-
"feature": ["add", "implement", "create", "new", "support", "feature"],
|
| 143 |
-
"refactor": ["refactor", "cleanup", "simplify", "reorganize", "rename"],
|
| 144 |
-
"documentation": ["doc", "readme", "comment", "documentation"],
|
| 145 |
-
"test": ["test", "spec", "fixture", "mock"],
|
| 146 |
-
"security": ["security", "vulnerability", "exploit", "cve", "auth"],
|
| 147 |
-
"performance": ["perf", "performance", "optimize", " faster", "speed"],
|
| 148 |
-
"revert": ["revert"],
|
| 149 |
-
"merge": ["merge"],
|
| 150 |
-
"chore": ["chore", "bump", "update"]
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
# Check for merge commits
|
| 154 |
-
if len(files_changed) == 0 and "merge" in subject_lower:
|
| 155 |
-
return "merge"
|
| 156 |
-
|
| 157 |
-
# Score each category
|
| 158 |
-
scores = defaultdict(int)
|
| 159 |
-
for category, keywords in patterns.items():
|
| 160 |
-
for keyword in keywords:
|
| 161 |
-
if keyword in text:
|
| 162 |
-
scores[category] += 1
|
| 163 |
-
|
| 164 |
-
# Get the highest scoring category
|
| 165 |
-
if scores:
|
| 166 |
-
best = max(scores, key=scores.get)
|
| 167 |
-
if scores[best] > 0:
|
| 168 |
-
return best
|
| 169 |
-
|
| 170 |
-
return "other"
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
for line in diff.split('\n'):
|
| 180 |
-
# File header
|
| 181 |
-
if line.startswith('+++ b/') or line.startswith('--- a/'):
|
| 182 |
-
if 'dev/null' not in line and 'index ' not in line:
|
| 183 |
-
current_file = line.replace('--- a/', '').replace('+++ b/', '').strip()
|
| 184 |
-
continue
|
| 185 |
-
|
| 186 |
-
# Hunk header
|
| 187 |
-
if line.startswith('@@'):
|
| 188 |
-
if current_file and current_hunk:
|
| 189 |
-
snippets.append({
|
| 190 |
-
"file": current_file,
|
| 191 |
-
"hunk": '\n'.join(current_hunk)
|
| 192 |
-
})
|
| 193 |
-
current_hunk = []
|
| 194 |
-
in_hunk = True
|
| 195 |
-
continue
|
| 196 |
-
|
| 197 |
-
# Added/removed lines
|
| 198 |
-
if in_hunk and (line.startswith('+') or line.startswith('-')):
|
| 199 |
-
current_hunk.append(line)
|
| 200 |
-
|
| 201 |
-
# Don't forget last hunk
|
| 202 |
-
if current_file and current_hunk and len(snippets) < max_snippets:
|
| 203 |
-
snippets.append({
|
| 204 |
-
"file": current_file,
|
| 205 |
-
"hunk": '\n'.join(current_hunk)
|
| 206 |
-
})
|
| 207 |
-
|
| 208 |
-
return snippets[:max_snippets]
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
"docker": ["Dockerfile", "docker-compose", "docker build"],
|
| 217 |
-
"git": ["git commit", "git push", "git pull", "git branch"],
|
| 218 |
-
"curl": ["curl ", "wget "],
|
| 219 |
-
"npm": ["npm ", "package.json"],
|
| 220 |
-
"pip": ["pip ", "requirements.txt"],
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
detected_tools = []
|
| 224 |
-
for tool, patterns in tool_indicators.items():
|
| 225 |
-
for pattern in patterns:
|
| 226 |
-
if pattern.lower() in diff.lower() or pattern.lower() in commit_message.lower():
|
| 227 |
-
detected_tools.append(tool)
|
| 228 |
-
break
|
| 229 |
-
|
| 230 |
-
if detected_tools:
|
| 231 |
-
return {
|
| 232 |
-
"tools": list(set(detected_tools)),
|
| 233 |
-
"is_automation": True
|
| 234 |
-
}
|
| 235 |
-
return None
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
# Skip if too few files changed (likely merge commit or trivial)
|
| 242 |
-
if stats["files_changed"] == 0:
|
| 243 |
-
return None
|
| 244 |
-
|
| 245 |
-
# Get the diff
|
| 246 |
-
diff = self.get_commit_diff(commit["hash"])
|
| 247 |
-
if not diff:
|
| 248 |
-
return None
|
| 249 |
-
|
| 250 |
-
# Classify the commit
|
| 251 |
-
files_changed = []
|
| 252 |
-
for line in diff.split('\n'):
|
| 253 |
-
if line.startswith('+++ b/') or line.startswith('--- a/'):
|
| 254 |
-
filename = line.replace('--- a/', '').replace('+++ b/', '').strip()
|
| 255 |
-
if 'dev/null' not in filename and 'index ' not in filename:
|
| 256 |
-
files_changed.append(filename)
|
| 257 |
-
|
| 258 |
-
commit_type = self.classify_commit(commit["subject"], commit["body"], files_changed)
|
| 259 |
-
|
| 260 |
-
# Extract code snippets
|
| 261 |
-
code_snippets = self.extract_code_snippets(diff)
|
| 262 |
-
|
| 263 |
-
# Detect tool patterns
|
| 264 |
-
tool_pattern = self.analyze_tool_patterns(diff, commit["subject"])
|
| 265 |
-
|
| 266 |
-
# Build pattern entry
|
| 267 |
-
pattern = {
|
| 268 |
-
"type": "git_commit_pattern",
|
| 269 |
-
"commit_hash": commit["hash"][:8],
|
| 270 |
-
"commit_type": commit_type,
|
| 271 |
-
"author": commit["author"],
|
| 272 |
-
"date": commit["date"],
|
| 273 |
-
"subject": commit["subject"],
|
| 274 |
-
"stats": {
|
| 275 |
-
"files_changed": stats["files_changed"],
|
| 276 |
-
"insertions": stats["insertions"],
|
| 277 |
-
"deletions": stats["deletions"],
|
| 278 |
-
"file_types": dict(stats["file_types"])
|
| 279 |
-
},
|
| 280 |
-
"code_snippets": code_snippets,
|
| 281 |
-
"tool_detection": tool_pattern,
|
| 282 |
-
"pattern_id": hashlib.md5(f"{commit['hash']}{commit['subject']}".encode()).hexdigest()[:12]
|
| 283 |
-
}
|
| 284 |
-
|
| 285 |
-
# Add success indicators (conventional commits, passing tests, etc.)
|
| 286 |
-
pattern["is_successful"] = self._is_successful_commit(commit, diff)
|
| 287 |
-
|
| 288 |
-
return pattern
|
| 289 |
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
return False
|
| 295 |
-
|
| 296 |
-
# Check for "fix" keywords followed by non-breaking changes
|
| 297 |
-
subject_lower = commit["subject"].lower()
|
| 298 |
-
if any(kw in subject_lower for kw in ["fix", "resolve", "solve"]):
|
| 299 |
-
return True
|
| 300 |
-
|
| 301 |
-
# Check if it's a refactor that simplifies code (more deletions than additions)
|
| 302 |
-
if "refactor" in subject_lower:
|
| 303 |
-
# We'd need to parse the diff more precisely, but roughly:
|
| 304 |
-
# if deletions > insertions, likely simplification
|
| 305 |
-
pass
|
| 306 |
-
|
| 307 |
-
# Assume most commits are successful unless they're clearly broken
|
| 308 |
-
# (e.g., "WIP", "TODO", "broken", "temp")
|
| 309 |
-
bad_words = ["wip", "todo", "broken", "temp", "hack", "quick fix"]
|
| 310 |
-
if any(word in subject_lower for word in bad_words):
|
| 311 |
-
return False
|
| 312 |
-
|
| 313 |
-
return True
|
| 314 |
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
continue
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
with open(output_path, 'w') as f:
|
| 373 |
-
for pattern in self.patterns:
|
| 374 |
-
f.write(json.dumps(pattern) + '\n')
|
| 375 |
-
|
| 376 |
-
print(f"\n💾 Saved patterns to: {output_path}")
|
| 377 |
-
|
| 378 |
-
# Also save a summary
|
| 379 |
-
summary_path = output_path.with_name(output_path.stem + '_summary.json')
|
| 380 |
-
summary = {
|
| 381 |
-
"total_patterns": len(self.patterns),
|
| 382 |
-
"by_type": dict(self.stats),
|
| 383 |
-
"extraction_date": datetime.now().isoformat(),
|
| 384 |
-
"repo": str(self.repo_path)
|
| 385 |
-
}
|
| 386 |
-
with open(summary_path, 'w') as f:
|
| 387 |
-
json.dump(summary, f, indent=2)
|
| 388 |
-
print(f"📊 Saved summary to: {summary_path}")
|
| 389 |
|
| 390 |
def main():
|
| 391 |
parser = argparse.ArgumentParser(
|
| 392 |
-
description="Extract patterns from Git
|
| 393 |
)
|
| 394 |
parser.add_argument(
|
| 395 |
-
"--repo",
|
| 396 |
type=str,
|
| 397 |
-
|
| 398 |
-
help="Path to
|
| 399 |
)
|
| 400 |
parser.add_argument(
|
| 401 |
"--output",
|
| 402 |
type=str,
|
| 403 |
-
|
| 404 |
-
help="Output file path
|
| 405 |
)
|
| 406 |
parser.add_argument(
|
| 407 |
-
"--
|
| 408 |
-
type=
|
| 409 |
-
default=
|
| 410 |
-
help="
|
| 411 |
-
)
|
| 412 |
-
parser.add_argument(
|
| 413 |
-
"--limit",
|
| 414 |
-
type=int,
|
| 415 |
-
help="Limit number of commits to process (for testing)"
|
| 416 |
)
|
| 417 |
|
| 418 |
args = parser.parse_args()
|
| 419 |
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
# Override commit limit by modifying the method
|
| 425 |
-
original_get_commit_history = extractor.get_commit_history
|
| 426 |
-
def limited_get_commit_history(branch, limit=None):
|
| 427 |
-
return original_get_commit_history(branch, limit=args.limit)
|
| 428 |
-
extractor.get_commit_history = limited_get_commit_history
|
| 429 |
-
|
| 430 |
-
patterns = extractor.extract_all_patterns()
|
| 431 |
-
|
| 432 |
-
if patterns:
|
| 433 |
-
extractor.save_patterns(Path(args.output))
|
| 434 |
-
|
| 435 |
-
# Show sample pattern
|
| 436 |
-
print("\n📋 Sample pattern:")
|
| 437 |
-
sample = patterns[0]
|
| 438 |
-
print(f" Type: {sample['commit_type']}")
|
| 439 |
-
print(f" Subject: {sample['subject']}")
|
| 440 |
-
print(f" Files: {sample['stats']['files_changed']} changed")
|
| 441 |
-
print(f" Insertions: {sample['stats']['insertions']}, Deletions: {sample['stats']['deletions']}")
|
| 442 |
-
if sample['tool_detection']:
|
| 443 |
-
print(f" Tools: {', '.join(sample['tool_detection']['tools'])}")
|
| 444 |
-
else:
|
| 445 |
-
print("\n⚠️ No patterns extracted. Try:")
|
| 446 |
-
print(" - Checking that the repository has commit history")
|
| 447 |
-
print(" - Increasing --limit or --min-commits")
|
| 448 |
-
print(" - Using a repository with more substantial commits")
|
| 449 |
-
|
| 450 |
-
except Exception as e:
|
| 451 |
-
print(f"❌ Error: {e}")
|
| 452 |
-
return 1
|
| 453 |
|
| 454 |
-
|
|
|
|
|
|
|
| 455 |
|
| 456 |
if __name__ == "__main__":
|
| 457 |
-
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
+
Extract Code Patterns from Git History
|
| 4 |
|
| 5 |
+
Scans Git commit history to identify bug fixes and feature additions,
|
| 6 |
+
extracting "before → after" patterns for training data generation.
|
|
|
|
| 7 |
|
| 8 |
Usage:
|
| 9 |
+
python extract_patterns_from_git.py --repo-path . --output patterns.jsonl
|
| 10 |
+
python extract_patterns_from_git.py --repo-path . --output patterns.jsonl --since-date "2024-01-01"
|
| 11 |
"""
|
| 12 |
|
|
|
|
|
|
|
| 13 |
import argparse
|
| 14 |
+
import hashlib
|
| 15 |
+
import json
|
| 16 |
+
import os
|
| 17 |
import subprocess
|
| 18 |
+
import sys
|
|
|
|
|
|
|
|
|
|
| 19 |
from datetime import datetime
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
from typing import Optional
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
except ImportError:
|
| 26 |
+
tqdm = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Keywords that indicate bug fixes or improvements
|
| 30 |
+
BUG_FIX_KEYWORDS = [
|
| 31 |
+
"fix", "bug", "hotfix", "patch", "resolve", "correct", "repair",
|
| 32 |
+
"error", "crash", "fail", "issue", "problem", "broken"
|
| 33 |
+
]
|
| 34 |
|
| 35 |
+
FEATURE_KEYWORDS = [
|
| 36 |
+
"feat", "feature", "add", "new", "implement", "enhance", "improve",
|
| 37 |
+
"optimize", "refactor", "support", "introduce"
|
| 38 |
+
]
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def is_text_file(filepath: str) -> bool:
|
| 42 |
+
"""Check if a file is likely a text file (not binary)."""
|
| 43 |
+
binary_extensions = {
|
| 44 |
+
'.pyc', '.so', '.dll', '.exe', '.bin', '.dat', '.pickle',
|
| 45 |
+
'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.ico', '.svg',
|
| 46 |
+
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.pdf', '.zip',
|
| 47 |
+
'.tar', '.gz', '.rar', '.7z', '.whl', '.egg',
|
| 48 |
+
'.class', '.jar', '.war', '.ear',
|
| 49 |
+
'.db', '.sqlite', '.sqlite3',
|
| 50 |
+
'.ttf', '.otf', '.woff', '.woff2',
|
| 51 |
+
'.pem', '.key', '.crt', '.cer',
|
| 52 |
+
'.DS_Store', '.gitignore'
|
| 53 |
+
}
|
| 54 |
|
| 55 |
+
ext = Path(filepath).suffix.lower()
|
| 56 |
+
if ext in binary_extensions:
|
| 57 |
+
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
+
# Try to read as text
|
| 60 |
+
try:
|
| 61 |
+
with open(filepath, 'rb') as f:
|
| 62 |
+
chunk = f.read(1024)
|
| 63 |
+
# Check for null bytes (common in binary files)
|
| 64 |
+
if b'\x00' in chunk:
|
| 65 |
+
return False
|
| 66 |
+
return True
|
| 67 |
+
except (OSError, IOError):
|
| 68 |
+
return False
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def get_commit_messages(repo_path: str, since_date: Optional[str] = None) -> list[dict]:
|
| 72 |
+
"""Get commit information from git log."""
|
| 73 |
+
cmd = ["git", "-C", repo_path, "log", "--pretty=format:%H|%s|%an|%ad|%ae", "--date=iso"]
|
| 74 |
|
| 75 |
+
if since_date:
|
| 76 |
+
cmd.extend([f"--since={since_date}"])
|
| 77 |
+
|
| 78 |
+
try:
|
| 79 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
commits = []
|
| 81 |
|
| 82 |
+
for line in result.stdout.strip().split('\n'):
|
| 83 |
+
if not line:
|
| 84 |
continue
|
| 85 |
+
parts = line.split('|')
|
| 86 |
+
if len(parts) >= 5:
|
|
|
|
| 87 |
commits.append({
|
| 88 |
+
'hash': parts[0],
|
| 89 |
+
'message': parts[1],
|
| 90 |
+
'author': parts[2],
|
| 91 |
+
'date': parts[3],
|
| 92 |
+
'email': parts[4] if len(parts) > 4 else ''
|
|
|
|
| 93 |
})
|
| 94 |
|
| 95 |
return commits
|
| 96 |
+
except subprocess.CalledProcessError as e:
|
| 97 |
+
print(f"Error reading git log: {e}", file=sys.stderr)
|
| 98 |
+
return []
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def get_changed_files(repo_path: str, commit_hash: str) -> list[str]:
|
| 102 |
+
"""Get list of files changed in a commit."""
|
| 103 |
+
cmd = ["git", "-C", repo_path, "diff-tree", "--no-commit-id", "--name-only", "-r", commit_hash]
|
| 104 |
|
| 105 |
+
try:
|
| 106 |
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
| 107 |
+
files = []
|
| 108 |
+
for line in result.stdout.strip().split('\n'):
|
| 109 |
+
if line.strip():
|
| 110 |
+
files.append(line.strip())
|
| 111 |
+
return files
|
| 112 |
+
except subprocess.CalledProcessError:
|
| 113 |
+
return []
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def get_file_diff(repo_path: str, commit_hash: str, filepath: str) -> tuple[Optional[str], Optional[str]]:
|
| 117 |
+
"""Get before and after content of a file in a commit."""
|
| 118 |
+
# Get the file content AFTER the commit
|
| 119 |
+
cmd_after = ["git", "-C", repo_path, "show", f"{commit_hash}:{filepath}"]
|
| 120 |
+
# Get the file content BEFORE the commit (parent)
|
| 121 |
+
cmd_before = ["git", "-C", repo_path, "show", f"{commit_hash}^:{filepath}"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
+
after_content = None
|
| 124 |
+
before_content = None
|
|
|
|
| 125 |
|
| 126 |
+
try:
|
| 127 |
+
result_after = subprocess.run(cmd_after, capture_output=True, text=True, check=True)
|
| 128 |
+
after_content = result_after.stdout
|
| 129 |
+
except subprocess.CalledProcessError:
|
| 130 |
+
# File might be new (no parent)
|
| 131 |
+
after_content = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
+
try:
|
| 134 |
+
result_before = subprocess.run(cmd_before, capture_output=True, text=True, check=True)
|
| 135 |
+
before_content = result_before.stdout
|
| 136 |
+
except subprocess.CalledProcessError:
|
| 137 |
+
# File was added in this commit
|
| 138 |
+
before_content = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
return before_content, after_content
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def infer_problem_type(message: str) -> str:
|
| 144 |
+
"""Infer the problem type from commit message."""
|
| 145 |
+
msg_lower = message.lower()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
+
# Check for bug fix indicators
|
| 148 |
+
for keyword in BUG_FIX_KEYWORDS:
|
| 149 |
+
if keyword in msg_lower:
|
| 150 |
+
return "bug_fix"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
|
| 152 |
+
# Check for feature indicators
|
| 153 |
+
for keyword in FEATURE_KEYWORDS:
|
| 154 |
+
if keyword in msg_lower:
|
| 155 |
+
return "feature_addition"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
+
return "unknown"
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def compute_confidence(message: str, before: Optional[str], after: Optional[str]) -> float:
|
| 161 |
+
"""Compute confidence score for the extracted pattern."""
|
| 162 |
+
confidence = 0.5 # Base confidence
|
| 163 |
+
|
| 164 |
+
# Higher confidence if message contains clear keywords
|
| 165 |
+
msg_lower = message.lower()
|
| 166 |
+
if any(k in msg_lower for k in ["fix", "bug", "hotfix", "patch"]):
|
| 167 |
+
confidence += 0.2
|
| 168 |
+
if any(k in msg_lower for k in ["feat", "feature", "add", "implement"]):
|
| 169 |
+
confidence += 0.15
|
| 170 |
+
|
| 171 |
+
# Higher confidence if we have both before and after
|
| 172 |
+
if before and after:
|
| 173 |
+
confidence += 0.15
|
| 174 |
+
elif before or after:
|
| 175 |
+
confidence += 0.05
|
| 176 |
+
|
| 177 |
+
# Higher confidence for substantial changes
|
| 178 |
+
if before and after:
|
| 179 |
+
content_len = max(len(before), len(after))
|
| 180 |
+
if content_len > 100:
|
| 181 |
+
confidence += 0.1
|
| 182 |
+
if content_len > 500:
|
| 183 |
+
confidence += 0.1
|
| 184 |
+
|
| 185 |
+
return min(confidence, 1.0)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def generate_pattern_id(commit_hash: str, filepath: str) -> str:
|
| 189 |
+
"""Generate a unique pattern ID."""
|
| 190 |
+
content = f"{commit_hash}:{filepath}"
|
| 191 |
+
return hashlib.sha256(content.encode()).hexdigest()[:16]
|
| 192 |
+
|
| 193 |
+
|
| 194 |
+
def extract_patterns(
|
| 195 |
+
repo_path: str,
|
| 196 |
+
output_path: str,
|
| 197 |
+
since_date: Optional[str] = None
|
| 198 |
+
) -> int:
|
| 199 |
+
"""Extract patterns from git history and write to JSONL file."""
|
| 200 |
+
|
| 201 |
+
print(f"Scanning repository: {repo_path}")
|
| 202 |
+
|
| 203 |
+
# Get all commits
|
| 204 |
+
commits = get_commit_messages(repo_path, since_date)
|
| 205 |
+
print(f"Found {len(commits)} commits")
|
| 206 |
+
|
| 207 |
+
if not commits:
|
| 208 |
+
print("No commits found.", file=sys.stderr)
|
| 209 |
+
return 0
|
| 210 |
+
|
| 211 |
+
patterns_extracted = 0
|
| 212 |
+
|
| 213 |
+
# Process each commit with progress bar
|
| 214 |
+
iterator = tqdm(commits, desc="Extracting patterns") if tqdm else commits
|
| 215 |
+
|
| 216 |
+
with open(output_path, 'w', encoding='utf-8') as outf:
|
| 217 |
+
for commit in iterator:
|
| 218 |
+
commit_hash = commit['hash']
|
| 219 |
+
message = commit['message']
|
| 220 |
+
author = commit['author']
|
| 221 |
+
date = commit['date']
|
| 222 |
+
|
| 223 |
+
# Infer problem type
|
| 224 |
+
problem_type = infer_problem_type(message)
|
| 225 |
+
|
| 226 |
+
# Skip if not a bug fix or feature
|
| 227 |
+
if problem_type == "unknown":
|
| 228 |
continue
|
| 229 |
+
|
| 230 |
+
# Get changed files
|
| 231 |
+
changed_files = get_changed_files(repo_path, commit_hash)
|
| 232 |
+
|
| 233 |
+
for filepath in changed_files:
|
| 234 |
+
# Skip binary files
|
| 235 |
+
full_path = os.path.join(repo_path, filepath)
|
| 236 |
+
if not os.path.exists(full_path):
|
| 237 |
+
continue
|
| 238 |
+
|
| 239 |
+
if not is_text_file(filepath):
|
| 240 |
+
continue
|
| 241 |
+
|
| 242 |
+
# Get diff
|
| 243 |
+
before_content, after_content = get_file_diff(repo_path, commit_hash, filepath)
|
| 244 |
+
|
| 245 |
+
# Skip if no meaningful change
|
| 246 |
+
if before_content == after_content:
|
| 247 |
+
continue
|
| 248 |
+
if not before_content and not after_content:
|
| 249 |
+
continue
|
| 250 |
+
|
| 251 |
+
# Compute confidence
|
| 252 |
+
confidence = compute_confidence(message, before_content, after_content)
|
| 253 |
+
|
| 254 |
+
# Create pattern record
|
| 255 |
+
pattern = {
|
| 256 |
+
"pattern_id": generate_pattern_id(commit_hash, filepath),
|
| 257 |
+
"problem_type": problem_type,
|
| 258 |
+
"before_code": before_content or "",
|
| 259 |
+
"after_code": after_content or "",
|
| 260 |
+
"commit_msg": message,
|
| 261 |
+
"author": author,
|
| 262 |
+
"date": date,
|
| 263 |
+
"confidence": round(confidence, 2)
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
# Write as JSONL
|
| 267 |
+
outf.write(json.dumps(pattern, ensure_ascii=False) + '\n')
|
| 268 |
+
patterns_extracted += 1
|
| 269 |
|
| 270 |
+
print(f"\nExtracted {patterns_extracted} patterns to {output_path}")
|
| 271 |
+
return patterns_extracted
|
| 272 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
def main():
|
| 275 |
parser = argparse.ArgumentParser(
|
| 276 |
+
description="Extract code patterns from Git history for training data"
|
| 277 |
)
|
| 278 |
parser.add_argument(
|
| 279 |
+
"--repo-path",
|
| 280 |
type=str,
|
| 281 |
+
required=True,
|
| 282 |
+
help="Path to the Git repository"
|
| 283 |
)
|
| 284 |
parser.add_argument(
|
| 285 |
"--output",
|
| 286 |
type=str,
|
| 287 |
+
required=True,
|
| 288 |
+
help="Output JSONL file path"
|
| 289 |
)
|
| 290 |
parser.add_argument(
|
| 291 |
+
"--since-date",
|
| 292 |
+
type=str,
|
| 293 |
+
default=None,
|
| 294 |
+
help="Only extract commits since this date (YYYY-MM-DD)"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
)
|
| 296 |
|
| 297 |
args = parser.parse_args()
|
| 298 |
|
| 299 |
+
# Validate repo path
|
| 300 |
+
if not os.path.isdir(os.path.join(args.repo_path, '.git')):
|
| 301 |
+
print(f"Error: {args.repo_path} is not a Git repository", file=sys.stderr)
|
| 302 |
+
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
|
| 304 |
+
# Run extraction
|
| 305 |
+
extract_patterns(args.repo_path, args.output, args.since_date)
|
| 306 |
+
|
| 307 |
|
| 308 |
if __name__ == "__main__":
|
| 309 |
+
main()
|
scripts/merge_lora_adapters.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Merge Multiple LoRA Adapters
|
| 4 |
+
|
| 5 |
+
Combines multiple LoRA adapters using weighted averaging based on success rates.
|
| 6 |
+
The merged adapter can be used to combine patterns learned by different users
|
| 7 |
+
or from different sources.
|
| 8 |
+
|
| 9 |
+
Usage:
|
| 10 |
+
python merge_lora_adapters.py \
|
| 11 |
+
--adapters adapter1.safetensors adapter2.safetensors \
|
| 12 |
+
--weights 0.6 0.4 \
|
| 13 |
+
--output merged.safetensors
|
| 14 |
+
|
| 15 |
+
# Or with success rates (auto-computes weights proportional to success)
|
| 16 |
+
python merge_lora_adapters.py \
|
| 17 |
+
--adapters adapter1.safetensors adapter2.safetensors \
|
| 18 |
+
--success-rates 0.85 0.65 \
|
| 19 |
+
--output merged.safetensors
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import json
|
| 24 |
+
import os
|
| 25 |
+
import sys
|
| 26 |
+
from pathlib import Path
|
| 27 |
+
from typing import Optional
|
| 28 |
+
|
| 29 |
+
# Try to import required libraries
|
| 30 |
+
try:
|
| 31 |
+
import torch
|
| 32 |
+
import torch.nn as nn
|
| 33 |
+
from safetensors.torch import load_file, save_file
|
| 34 |
+
HAS_LIBS = True
|
| 35 |
+
except ImportError:
|
| 36 |
+
HAS_LIBS = False
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def load_adapter(path: str) -> dict:
|
| 40 |
+
"""Load a LoRA adapter from a safetensors file."""
|
| 41 |
+
if not os.path.exists(path):
|
| 42 |
+
raise FileNotFoundError(f"Adapter not found: {path}")
|
| 43 |
+
|
| 44 |
+
return load_file(path)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def compute_weights_from_success_rates(success_rates: list[float]) -> list[float]:
|
| 48 |
+
"""Compute normalized weights proportional to success rates."""
|
| 49 |
+
total = sum(success_rates)
|
| 50 |
+
if total == 0:
|
| 51 |
+
# Equal weights if all success rates are 0
|
| 52 |
+
return [1.0 / len(success_rates)] * len(success_rates)
|
| 53 |
+
return [rate / total for rate in success_rates]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def merge_adapters_weighted(
|
| 57 |
+
adapters: list[dict],
|
| 58 |
+
weights: list[float],
|
| 59 |
+
output_path: str
|
| 60 |
+
) -> dict:
|
| 61 |
+
"""
|
| 62 |
+
Merge multiple LoRA adapters using weighted averaging.
|
| 63 |
+
|
| 64 |
+
Algorithm: merged_weight = Σ(adapter_i.weight * adapter_i.success_rate) / Σ(success_rate)
|
| 65 |
+
|
| 66 |
+
For simplicity, we use the provided weights directly.
|
| 67 |
+
"""
|
| 68 |
+
if len(adapters) != len(weights):
|
| 69 |
+
raise ValueError("Number of adapters must match number of weights")
|
| 70 |
+
|
| 71 |
+
# Normalize weights
|
| 72 |
+
total_weight = sum(weights)
|
| 73 |
+
if total_weight == 0:
|
| 74 |
+
raise ValueError("Sum of weights cannot be zero")
|
| 75 |
+
normalized_weights = [w / total_weight for w in weights]
|
| 76 |
+
|
| 77 |
+
print(f"Merging {len(adapters)} adapters with weights: {normalized_weights}")
|
| 78 |
+
|
| 79 |
+
# Get all keys from the first adapter
|
| 80 |
+
sample_adapter = adapters[0]
|
| 81 |
+
all_keys = set(sample_adapter.keys())
|
| 82 |
+
|
| 83 |
+
# Verify all adapters have the same keys
|
| 84 |
+
for i, adapter in enumerate(adapters[1:], 1):
|
| 85 |
+
adapter_keys = set(adapter.keys())
|
| 86 |
+
if adapter_keys != all_keys:
|
| 87 |
+
print(f"Warning: Adapter {i} has different keys. Taking union.", file=sys.stderr)
|
| 88 |
+
all_keys = all_keys.union(adapter_keys)
|
| 89 |
+
|
| 90 |
+
# Merge each tensor
|
| 91 |
+
merged = {}
|
| 92 |
+
for key in all_keys:
|
| 93 |
+
# Collect tensors from all adapters
|
| 94 |
+
tensors = []
|
| 95 |
+
valid_weights = []
|
| 96 |
+
|
| 97 |
+
for i, (adapter, weight) in enumerate(zip(adapters, normalized_weights)):
|
| 98 |
+
if key in adapter:
|
| 99 |
+
tensors.append(adapter[key])
|
| 100 |
+
valid_weights.append(weight)
|
| 101 |
+
|
| 102 |
+
if not tensors:
|
| 103 |
+
continue
|
| 104 |
+
|
| 105 |
+
# Normalize weights for available tensors
|
| 106 |
+
total_valid = sum(valid_weights)
|
| 107 |
+
if total_valid == 0:
|
| 108 |
+
continue
|
| 109 |
+
norm_weights = [w / total_valid for w in valid_weights]
|
| 110 |
+
|
| 111 |
+
# Weighted average
|
| 112 |
+
merged[key] = sum(t * w for t, w in zip(tensors, norm_weights))
|
| 113 |
+
|
| 114 |
+
# Save merged adapter
|
| 115 |
+
save_file(merged, output_path)
|
| 116 |
+
print(f"Merged adapter saved to: {output_path}")
|
| 117 |
+
|
| 118 |
+
return merged
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def compute_adapter_stats(adapter: dict) -> dict:
|
| 122 |
+
"""Compute statistics about an adapter for debugging."""
|
| 123 |
+
stats = {
|
| 124 |
+
"num_tensors": len(adapter),
|
| 125 |
+
"total_params": 0,
|
| 126 |
+
"dtype_counts": {},
|
| 127 |
+
"shape_counts": {}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
for key, tensor in adapter.items():
|
| 131 |
+
num_params = tensor.numel()
|
| 132 |
+
stats["total_params"] += num_params
|
| 133 |
+
|
| 134 |
+
dtype = str(tensor.dtype)
|
| 135 |
+
stats["dtype_counts"][dtype] = stats["dtype_counts"].get(dtype, 0) + 1
|
| 136 |
+
|
| 137 |
+
shape = tuple(tensor.shape)
|
| 138 |
+
shape_key = str(shape)
|
| 139 |
+
stats["shape_counts"][shape_key] = stats["shape_counts"].get(shape_key, 0) + 1
|
| 140 |
+
|
| 141 |
+
return stats
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def main():
|
| 145 |
+
parser = argparse.ArgumentParser(
|
| 146 |
+
description="Merge multiple LoRA adapters using weighted averaging"
|
| 147 |
+
)
|
| 148 |
+
parser.add_argument(
|
| 149 |
+
"--adapters",
|
| 150 |
+
type=str,
|
| 151 |
+
nargs="+",
|
| 152 |
+
required=True,
|
| 153 |
+
help="Paths to LoRA adapter safetensors files"
|
| 154 |
+
)
|
| 155 |
+
parser.add_argument(
|
| 156 |
+
"--weights",
|
| 157 |
+
type=float,
|
| 158 |
+
nargs="+",
|
| 159 |
+
default=None,
|
| 160 |
+
help="Manual weights for each adapter (must sum to 1 or will be normalized)"
|
| 161 |
+
)
|
| 162 |
+
parser.add_argument(
|
| 163 |
+
"--success-rates",
|
| 164 |
+
type=float,
|
| 165 |
+
nargs="+",
|
| 166 |
+
default=None,
|
| 167 |
+
help="Success rates for each adapter (weights computed proportionally)"
|
| 168 |
+
)
|
| 169 |
+
parser.add_argument(
|
| 170 |
+
"--output",
|
| 171 |
+
type=str,
|
| 172 |
+
required=True,
|
| 173 |
+
help="Output path for merged adapter"
|
| 174 |
+
)
|
| 175 |
+
parser.add_argument(
|
| 176 |
+
"--stats",
|
| 177 |
+
action="store_true",
|
| 178 |
+
help="Print adapter statistics"
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
+
args = parser.parse_args()
|
| 182 |
+
|
| 183 |
+
if not HAS_LIBS:
|
| 184 |
+
print("Error: Required libraries not found.", file=sys.stderr)
|
| 185 |
+
print("Install with: pip install torch safetensors", file=sys.stderr)
|
| 186 |
+
sys.exit(1)
|
| 187 |
+
|
| 188 |
+
# Validate inputs
|
| 189 |
+
if args.weights and args.success_rates:
|
| 190 |
+
print("Error: Cannot specify both --weights and --success-rates", file=sys.stderr)
|
| 191 |
+
sys.exit(1)
|
| 192 |
+
|
| 193 |
+
if args.weights:
|
| 194 |
+
if len(args.adapters) != len(args.weights):
|
| 195 |
+
print("Error: Number of --adapters must match number of --weights", file=sys.stderr)
|
| 196 |
+
sys.exit(1)
|
| 197 |
+
weights = args.weights
|
| 198 |
+
elif args.success_rates:
|
| 199 |
+
if len(args.adapters) != len(args.success_rates):
|
| 200 |
+
print("Error: Number of --adapters must match number of --success-rates", file=sys.stderr)
|
| 201 |
+
sys.exit(1)
|
| 202 |
+
weights = compute_weights_from_success_rates(args.success_rates)
|
| 203 |
+
print(f"Computed weights from success rates: {weights}")
|
| 204 |
+
else:
|
| 205 |
+
# Equal weights
|
| 206 |
+
weights = [1.0 / len(args.adapters)] * len(args.adapters)
|
| 207 |
+
|
| 208 |
+
# Load adapters
|
| 209 |
+
print(f"Loading {len(args.adapters)} adapters...")
|
| 210 |
+
adapters = []
|
| 211 |
+
for i, path in enumerate(args.adapters):
|
| 212 |
+
print(f" Loading {i+1}: {path}")
|
| 213 |
+
adapter = load_adapter(path)
|
| 214 |
+
adapters.append(adapter)
|
| 215 |
+
|
| 216 |
+
if args.stats:
|
| 217 |
+
stats = compute_adapter_stats(adapter)
|
| 218 |
+
print(f" Stats: {stats['num_tensors']} tensors, {stats['total_params']:,} params")
|
| 219 |
+
|
| 220 |
+
# Merge
|
| 221 |
+
merge_adapters_weighted(adapters, weights, args.output)
|
| 222 |
+
|
| 223 |
+
# Print merge info
|
| 224 |
+
print(f"\nMerge complete!")
|
| 225 |
+
print(f" Output: {args.output}")
|
| 226 |
+
print(f" Adapters merged: {len(args.adapters)}")
|
| 227 |
+
|
| 228 |
+
# Save merge metadata
|
| 229 |
+
metadata_path = args.output + ".meta.json"
|
| 230 |
+
metadata = {
|
| 231 |
+
"adapters": args.adapters,
|
| 232 |
+
"weights": weights,
|
| 233 |
+
"num_adapters": len(args.adapters)
|
| 234 |
+
}
|
| 235 |
+
with open(metadata_path, 'w') as f:
|
| 236 |
+
json.dump(metadata, f, indent=2)
|
| 237 |
+
print(f" Metadata: {metadata_path}")
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
if __name__ == "__main__":
|
| 241 |
+
main()
|
vastai_deploy.sh
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# =============================================================================
|
| 3 |
+
# vastai_deploy.sh - Deploy Stack 2.9 Training on Vast.ai
|
| 4 |
+
# =============================================================================
|
| 5 |
+
#
|
| 6 |
+
# USAGE:
|
| 7 |
+
# ./vastai_deploy.sh [--mode train|inference] [--config CONFIG] [--gpu GPU_NAME]
|
| 8 |
+
# ./vastai_deploy.sh [--list-gpus] [--ssh INSTANCE_ID]
|
| 9 |
+
#
|
| 10 |
+
# EXAMPLES:
|
| 11 |
+
# # Find and launch a training instance with A100 80GB
|
| 12 |
+
# ./vastai_deploy.sh --mode train --gpu A100-80
|
| 13 |
+
#
|
| 14 |
+
# # Launch inference on RTX 4090
|
| 15 |
+
# ./vastai_deploy.sh --mode inference --gpu RTX-4090
|
| 16 |
+
#
|
| 17 |
+
# # SSH into running instance
|
| 18 |
+
# ./vastai_deploy.sh --ssh 123456
|
| 19 |
+
#
|
| 20 |
+
# # List available GPU instances
|
| 21 |
+
# ./vastai_deploy.sh --list-gpus
|
| 22 |
+
#
|
| 23 |
+
# PREREQUISITES:
|
| 24 |
+
# - vastai CLI installed: pip install vastai
|
| 25 |
+
# - Vast.ai account with API key: vastai auth
|
| 26 |
+
# - SSH key configured: vastai create-key
|
| 27 |
+
# - HF_TOKEN set for gated models
|
| 28 |
+
#
|
| 29 |
+
# =============================================================================
|
| 30 |
+
|
| 31 |
+
set -euo pipefail
|
| 32 |
+
|
| 33 |
+
# ------------------------------ Defaults -------------------------------------
|
| 34 |
+
MODE="${MODE:-train}"
|
| 35 |
+
CONFIG_PATH="${CONFIG_PATH:-./stack_2_9_training/train_config.yaml}"
|
| 36 |
+
GPU_NAME="${GPU_NAME:-A100-80}"
|
| 37 |
+
MIN_VRAM_GB="${MIN_VRAM_GB:-40}"
|
| 38 |
+
MIN_DL_SPEED="${MIN_DL_SPEED:-800}" # MB/s
|
| 39 |
+
MIN_CPU="${MIN_CPU:-8}"
|
| 40 |
+
SSH_KEY="${SSH_KEY:-}" # Leave empty to auto-detect
|
| 41 |
+
REPO_URL="${REPO_URL:-https://github.com/walidsobhie-code/ai-voice-clone.git}"
|
| 42 |
+
REPO_BRANCH="${REPO_BRANCH:-main}"
|
| 43 |
+
LOG_FILE="${LOG_FILE:-~/vastai_stack29.log}"
|
| 44 |
+
INSTANCE_ID=""
|
| 45 |
+
|
| 46 |
+
# ------------------------------ Helpers --------------------------------------
|
| 47 |
+
usage() {
|
| 48 |
+
grep "^#" "$0" | sed 's/^# //;s/^#//'
|
| 49 |
+
exit 1
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"; }
|
| 53 |
+
error() { log "ERROR: $*" >&2; exit 1; }
|
| 54 |
+
|
| 55 |
+
require_cmd() {
|
| 56 |
+
command -v "$1" &>/dev/null || error "Required command not found: $1"
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
# GPU name map: friendly -> vastai search string
|
| 60 |
+
declare -A GPU_SEARCH_MAP
|
| 61 |
+
GPU_SEARCH_MAP["A100-80"]="A100 80GB"
|
| 62 |
+
GPU_SEARCH_MAP["A100-40"]="A100 40GB"
|
| 63 |
+
GPU_SEARCH_MAP["H100"]="H100"
|
| 64 |
+
GPU_SEARCH_MAP["RTX-4090"]="RTX 4090"
|
| 65 |
+
GPU_SEARCH_MAP["RTX-3090"]="RTX 3090"
|
| 66 |
+
|
| 67 |
+
# ------------------------------ Parse Args ----------------------------------
|
| 68 |
+
while [[ $# -gt 0 ]]; do
|
| 69 |
+
case $1 in
|
| 70 |
+
--mode) MODE="$2"; shift 2 ;;
|
| 71 |
+
--config) CONFIG_PATH="$2"; shift 2 ;;
|
| 72 |
+
--gpu) GPU_NAME="$2"; shift 2 ;;
|
| 73 |
+
--ssh) INSTANCE_ID="$2"; shift 2 ;;
|
| 74 |
+
--list-gpus) LIST_GPUS=true; shift ;;
|
| 75 |
+
--help|-h) usage ;;
|
| 76 |
+
*) error "Unknown option: $1" ;;
|
| 77 |
+
esac
|
| 78 |
+
done
|
| 79 |
+
|
| 80 |
+
# --------------------------------- List GPUs ---------------------------------
|
| 81 |
+
if [[ "${LIST_GPUS:-false}" == "true" ]]; then
|
| 82 |
+
log "Fetching available GPU offers..."
|
| 83 |
+
vastai search instances "" --gpu "${GPU_SEARCH_MAP[$GPU_NAME]:-$GPU_NAME}" \
|
| 84 |
+
--order "dph_total" \
|
| 85 |
+
--num 20 2>/dev/null || vastai search offers "" 2>/dev/null
|
| 86 |
+
exit 0
|
| 87 |
+
fi
|
| 88 |
+
|
| 89 |
+
# --------------------------------- SSH into Instance ------------------------
|
| 90 |
+
if [[ -n "$INSTANCE_ID" ]]; then
|
| 91 |
+
log "Connecting to instance $INSTANCE_ID..."
|
| 92 |
+
ssh -o StrictHostKeyChecking=no "instance${INSTANCE_ID}@console.vast.ai"
|
| 93 |
+
exit 0
|
| 94 |
+
fi
|
| 95 |
+
|
| 96 |
+
# Validate mode
|
| 97 |
+
if [[ "$MODE" != "train" && "$MODE" != "inference" ]]; then
|
| 98 |
+
error "Mode must be 'train' or 'inference', got: $MODE"
|
| 99 |
+
fi
|
| 100 |
+
|
| 101 |
+
# ------------------------------ Prerequisites --------------------------------
|
| 102 |
+
log "Checking prerequisites..."
|
| 103 |
+
require_cmd vastai
|
| 104 |
+
|
| 105 |
+
# ------------------------------ Find Suitable Instance -----------------------
|
| 106 |
+
SEARCH_TERM="${GPU_SEARCH_MAP[$GPU_NAME]:-$GPU_NAME}"
|
| 107 |
+
log "Searching for GPU: $SEARCH_TERM (min VRAM: ${MIN_VRAM_GB}GB)..."
|
| 108 |
+
|
| 109 |
+
# Query available offers
|
| 110 |
+
# Using: vastai search offers <query>
|
| 111 |
+
OFFERS=$(vastai search offers "$SEARCH_TERM" 2>/dev/null || echo "")
|
| 112 |
+
|
| 113 |
+
if [[ -z "$OFFERS" ]]; then
|
| 114 |
+
error "No offers found for GPU: $GPU_NAME. Try --list-gpus to see available options."
|
| 115 |
+
fi
|
| 116 |
+
|
| 117 |
+
# Parse best offer (lowest price, meets requirements)
|
| 118 |
+
# Extract the first offer that meets VRAM requirements
|
| 119 |
+
BEST_OFFER=$(echo "$OFFERS" | awk -v min_vram="$MIN_VRAM_GB" '
|
| 120 |
+
/^[0-9]/ {
|
| 121 |
+
# Very rough parsing - in production use jq with vastai API
|
| 122 |
+
# This is a simplified heuristic
|
| 123 |
+
}
|
| 124 |
+
' | head -1)
|
| 125 |
+
|
| 126 |
+
# Simpler approach: use the CLI directly with filters
|
| 127 |
+
log "Finding best available instance..."
|
| 128 |
+
|
| 129 |
+
# Create instance with inline args
|
| 130 |
+
# See: https://docs.vast.ai/cli/#creating-an-instance
|
| 131 |
+
CREATE_CMD="vastai create instance \
|
| 132 |
+
--gpu \"$SEARCH_TERM\" \
|
| 133 |
+
--min-dl-speed $MIN_DL_SPEED \
|
| 134 |
+
--min-cpu-cores $MIN_CPU \
|
| 135 |
+
--onstart-url https://raw.githubusercontent.com/walidsobhie-code/ai-voice-clone/main/vastai_onstart.sh \
|
| 136 |
+
--image nvidia/cuda:12.1.0-runtime-ubuntu22.04 \
|
| 137 |
+
--force-yes"
|
| 138 |
+
|
| 139 |
+
log "Would run: $CREATE_CMD"
|
| 140 |
+
log ""
|
| 141 |
+
log "NOTE: Vast.ai interactive mode recommended. Run the following manually:"
|
| 142 |
+
log ""
|
| 143 |
+
log " # Search for available instances:"
|
| 144 |
+
log " vastai search offers \"${GPU_SEARCH_MAP[$GPU_NAME]:-$GPU_NAME}\""
|
| 145 |
+
log ""
|
| 146 |
+
log " # Launch an instance:"
|
| 147 |
+
log " vastai create instance \\"
|
| 148 |
+
log " --gpu ${GPU_SEARCH_MAP[$GPU_NAME]:-$GPU_NAME} \\"
|
| 149 |
+
log " --image nvidia/cuda:12.1.0-runtime-ubuntu22.04 \\"
|
| 150 |
+
log " --min-dl-speed $MIN_DL_SPEED \\"
|
| 151 |
+
log " --ssh-key $(ssh-add -L 2>/dev/null | cut -d' ' -f2 | head -1 || echo 'YOUR_SSH_KEY_ID')"
|
| 152 |
+
log ""
|
| 153 |
+
log " # Then SSH in and run training manually (see below)"
|
| 154 |
+
log ""
|
| 155 |
+
log " # Or use this script in interactive mode with TMUX:"
|
| 156 |
+
log " tmux new-session -d -s stack29 'bash'"
|
| 157 |
+
log ""
|
| 158 |
+
|
| 159 |
+
# ------------------------------ Training/Inference Script ---------------------
|
| 160 |
+
log "Creating deployment script for instance..."
|
| 161 |
+
|
| 162 |
+
DEPLOY_SCRIPT="/tmp/stack29_deploy.sh"
|
| 163 |
+
cat > "$DEPLOY_SCRIPT" << 'DEPLOY_EOF'
|
| 164 |
+
#!/bin/bash
|
| 165 |
+
set -euo pipefail
|
| 166 |
+
|
| 167 |
+
MODE="${1:-train}"
|
| 168 |
+
CONFIG_PATH="${2:-./stack_2_9_training/train_config.yaml}"
|
| 169 |
+
LOGFILE="/root/stack29_$(date +%Y%m%d_%H%M%S).log"
|
| 170 |
+
HF_TOKEN="${HF_TOKEN:-}"
|
| 171 |
+
|
| 172 |
+
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOGFILE"; }
|
| 173 |
+
|
| 174 |
+
log "=== Stack 2.9 Deployment Started ==="
|
| 175 |
+
log "Mode: $MODE"
|
| 176 |
+
log "Config: $CONFIG_PATH"
|
| 177 |
+
log "Log: $LOGFILE"
|
| 178 |
+
log "Hostname: $(hostname)"
|
| 179 |
+
log "GPU: $(nvidia-smi --query-gpu=name,memory.total --format=csv 2>/dev/null || echo 'nvidia-smi not found')"
|
| 180 |
+
log ""
|
| 181 |
+
|
| 182 |
+
# ---- Env setup ----
|
| 183 |
+
export HF_TOKEN="${HF_TOKEN}"
|
| 184 |
+
export PYTORCH_CUDA_ALLOC_CONF="max_split_size_mb=512"
|
| 185 |
+
export TRANSFORMERS_CACHE="/data/hf_cache"
|
| 186 |
+
export HF_HOME="/data/hf_cache"
|
| 187 |
+
export CUDA_VISIBLE_DEVICES="0"
|
| 188 |
+
|
| 189 |
+
mkdir -p /data/hf_cache /data/outputs /data/adapters
|
| 190 |
+
|
| 191 |
+
# ---- Install deps ----
|
| 192 |
+
log "Installing system packages..."
|
| 193 |
+
apt-get update -qq && apt-get install -y -qq \
|
| 194 |
+
git curl wget build-essential libsndfile1 ffmpeg \
|
| 195 |
+
2>&1 | tail -3
|
| 196 |
+
|
| 197 |
+
log "Installing Python packages..."
|
| 198 |
+
pip install --upgrade pip -q
|
| 199 |
+
pip install -q \
|
| 200 |
+
torch \
|
| 201 |
+
transformers \
|
| 202 |
+
peft \
|
| 203 |
+
accelerate \
|
| 204 |
+
bitsandbytes \
|
| 205 |
+
datasets \
|
| 206 |
+
trl \
|
| 207 |
+
scipy \
|
| 208 |
+
soundfile \
|
| 209 |
+
librosa \
|
| 210 |
+
pyyaml \
|
| 211 |
+
tqdm \
|
| 212 |
+
gradio \
|
| 213 |
+
fastapi \
|
| 214 |
+
uvicorn \
|
| 215 |
+
2>&1 | tail -5
|
| 216 |
+
|
| 217 |
+
# ---- Clone repo ----
|
| 218 |
+
log "Cloning repository..."
|
| 219 |
+
cd /data
|
| 220 |
+
if [[ ! -d "ai-voice-clone" ]]; then
|
| 221 |
+
git clone --depth 1 -b main https://github.com/walidsobhie-code/ai-voice-clone.git ai-voice-clone
|
| 222 |
+
fi
|
| 223 |
+
cd ai-voice-clone
|
| 224 |
+
|
| 225 |
+
# Copy config if custom
|
| 226 |
+
if [[ "$CONFIG_PATH" != "./stack_2_9_training/train_config.yaml" ]]; then
|
| 227 |
+
cp "$CONFIG_PATH" ./stack_2_9_training/train_config.yaml
|
| 228 |
+
fi
|
| 229 |
+
|
| 230 |
+
log "Repository ready. Starting application..."
|
| 231 |
+
|
| 232 |
+
# ---- Start Training or Inference ----
|
| 233 |
+
if [[ "$MODE" == "train" ]]; then
|
| 234 |
+
log "Starting LoRA training..."
|
| 235 |
+
log "Command: python -m stack_2_9_training.train_lora --config ./stack_2_9_training/train_config.yaml"
|
| 236 |
+
python -m stack_2_9_training.train_lora \
|
| 237 |
+
--config ./stack_2_9_training/train_config.yaml \
|
| 238 |
+
2>&1 | tee -a "$LOGFILE"
|
| 239 |
+
else
|
| 240 |
+
log "Starting inference server..."
|
| 241 |
+
log "Command: python -m uvicorn stack.serve:app --host 0.0.0.0 --port 7860"
|
| 242 |
+
python -m uvicorn \
|
| 243 |
+
stack.serve:app \
|
| 244 |
+
--host 0.0.0.0 \
|
| 245 |
+
--port 7860 \
|
| 246 |
+
2>&1 | tee -a "$LOGFILE"
|
| 247 |
+
fi
|
| 248 |
+
DEPLOY_EOF
|
| 249 |
+
|
| 250 |
+
chmod +x "$DEPLOY_SCRIPT"
|
| 251 |
+
log "Deploy script written to: $DEPLOY_SCRIPT"
|
| 252 |
+
log "Contents will be transferred to the instance on creation."
|
| 253 |
+
|
| 254 |
+
# ------------------------------ Full Create Instructions ---------------------
|
| 255 |
+
log ""
|
| 256 |
+
log "=== Full Vast.ai Deployment Instructions ==="
|
| 257 |
+
log ""
|
| 258 |
+
log "1. Find a suitable instance:"
|
| 259 |
+
log " vastai search offers \"${GPU_SEARCH_MAP[$GPU_NAME]:-$GPU_NAME}\""
|
| 260 |
+
log ""
|
| 261 |
+
log "2. Create the instance (note the offer ID from step 1):"
|
| 262 |
+
log " vastai create instance --offer-id <id> \\"
|
| 263 |
+
log " --image nvidia/cuda:12.1.0-devel-ubuntu22.04 \\"
|
| 264 |
+
log " --ssh-key <your-ssh-key> \\"
|
| 265 |
+
log " --onstart-url https://raw.githubusercontent.com/walidsobhie-code/ai-voice-clone/main/vastai_onstart.sh \\"
|
| 266 |
+
log " --onstart-cmd '$MODE /data/ai-voice-clone/stack_2_9_training/train_config.yaml'"
|
| 267 |
+
log ""
|
| 268 |
+
log "3. SSH into the instance after it starts:"
|
| 269 |
+
log " vastai ssh <instance-id>"
|
| 270 |
+
log ""
|
| 271 |
+
log "4. Or use screen/tmux for persistent sessions:"
|
| 272 |
+
log " screen -S stack29"
|
| 273 |
+
log " bash /tmp/stack29_deploy.sh $MODE $CONFIG_PATH"
|
| 274 |
+
log " # Ctrl+A D to detach"
|
| 275 |
+
log ""
|
| 276 |
+
log "5. Monitor training:"
|
| 277 |
+
log " tail -f $LOGFILE"
|
| 278 |
+
log " nvidia-smi -l 1"
|
| 279 |
+
log ""
|
| 280 |
+
log "=== Clean Shutdown ==="
|
| 281 |
+
log "To stop training gracefully:"
|
| 282 |
+
log " # Find the process"
|
| 283 |
+
log " ps aux | grep train_lora"
|
| 284 |
+
log " # Send SIGTERM for graceful shutdown"
|
| 285 |
+
log " kill -SIGTERM <pid>"
|
| 286 |
+
log ""
|
| 287 |
+
log "To stop and destroy the instance:"
|
| 288 |
+
log " vastai destroy instance <instance-id>"
|