javasop commited on
Commit
dadbb83
·
verified ·
1 Parent(s): 400bdf6

Upload folder using huggingface_hub

Browse files
Files changed (8) hide show
  1. Dockerfile +23 -0
  2. README.md +98 -10
  3. config.yaml +55 -0
  4. evaluate.py +254 -0
  5. generate.py +207 -0
  6. requirements.txt +15 -0
  7. train.py +211 -0
  8. utils/__init__.py +1 -0
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-devel
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ git \
8
+ curl \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements and install Python dependencies
12
+ COPY requirements.txt .
13
+ RUN pip install --no-cache-dir -r requirements.txt
14
+
15
+ # Copy training code
16
+ COPY . .
17
+
18
+ # Set environment variables
19
+ ENV PYTHONUNBUFFERED=1
20
+ ENV HF_HOME=/app/.cache/huggingface
21
+
22
+ # Default command
23
+ CMD ["python", "train.py", "--config", "config.yaml"]
README.md CHANGED
@@ -1,10 +1,98 @@
1
- ---
2
- title: Orbgen Training
3
- emoji: 🚀
4
- colorFrom: yellow
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OrbGen Training
2
+
3
+ Training code for OrbGen - a model that generates valid Orbital schemas from natural language.
4
+
5
+ ## Quick Start
6
+
7
+ ### 1. Install Dependencies
8
+
9
+ ```bash
10
+ pip install -r requirements.txt
11
+ ```
12
+
13
+ ### 2. Configure Training
14
+
15
+ Edit `config.yaml` to adjust:
16
+ - Base model
17
+ - Training hyperparameters
18
+ - LoRA configuration
19
+ - W&B settings
20
+
21
+ ### 3. Train
22
+
23
+ ```bash
24
+ # Full training
25
+ python train.py --config config.yaml
26
+
27
+ # Debug mode (1 epoch, no W&B)
28
+ python train.py --config config.yaml --debug
29
+
30
+ # Resume from checkpoint
31
+ python train.py --config config.yaml --resume_from_checkpoint ./orbgen-1.5b/checkpoint-500
32
+ ```
33
+
34
+ ### 4. Evaluate
35
+
36
+ ```bash
37
+ # Basic evaluation
38
+ python evaluate.py --checkpoint ./orbgen-1.5b/final
39
+
40
+ # With Orbital validator
41
+ python evaluate.py --checkpoint ./orbgen-1.5b/final --use_validator
42
+ ```
43
+
44
+ ### 5. Generate
45
+
46
+ ```bash
47
+ # Single generation
48
+ python generate.py --prompt "Create a task management app with projects and due dates"
49
+
50
+ # Interactive mode
51
+ python generate.py --interactive
52
+
53
+ # Save to file
54
+ python generate.py --prompt "..." --output schema.orb --validate
55
+ ```
56
+
57
+ ## Files
58
+
59
+ | File | Description |
60
+ |------|-------------|
61
+ | `train.py` | Main training script with SFT |
62
+ | `evaluate.py` | Evaluation with Orbital validation |
63
+ | `generate.py` | Inference and generation |
64
+ | `config.yaml` | Training configuration |
65
+ | `Dockerfile` | Container for training |
66
+ | `requirements.txt` | Python dependencies |
67
+
68
+ ## Training on HuggingFace Spaces
69
+
70
+ 1. Push this directory to `orbital-ai/orbgen-training`:
71
+ ```bash
72
+ hf upload orbital-ai/orbgen-training . --repo-type space
73
+ ```
74
+
75
+ 2. Configure the Space with A10G GPU
76
+
77
+ 3. Training will start automatically
78
+
79
+ ## Hardware Requirements
80
+
81
+ | Phase | GPU | VRAM | Time |
82
+ |-------|-----|------|------|
83
+ | Training (3 epochs) | A10G | 24GB | ~6 hours |
84
+ | Evaluation | T4 | 16GB | ~30 min |
85
+ | Inference | T4 | 16GB | ~1 sec/gen |
86
+
87
+ ## Model Output
88
+
89
+ After training, the model is saved to `./orbgen-1.5b/final/`:
90
+ - `adapter_config.json` - LoRA configuration
91
+ - `adapter_model.safetensors` - LoRA weights
92
+ - `tokenizer.json` - Tokenizer
93
+ - `config.json` - Model config
94
+
95
+ To push to HuggingFace:
96
+ ```bash
97
+ hf upload orbital-ai/orbgen-1.5b ./orbgen-1.5b/final --repo-type model
98
+ ```
config.yaml ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OrbGen Training Configuration
2
+
3
+ model:
4
+ base_model: "Qwen/Qwen2.5-Coder-1.5B"
5
+ output_dir: "./orbgen-1.5b"
6
+ max_seq_length: 8192
7
+
8
+ data:
9
+ dataset: "orbital-ai/orbital-schemas"
10
+ train_split: "train"
11
+ eval_split: "validation"
12
+
13
+ training:
14
+ # SFT Configuration
15
+ num_epochs: 3
16
+ per_device_train_batch_size: 2
17
+ per_device_eval_batch_size: 2
18
+ gradient_accumulation_steps: 8
19
+ learning_rate: 2.0e-5
20
+ warmup_ratio: 0.1
21
+ weight_decay: 0.01
22
+ max_grad_norm: 1.0
23
+
24
+ # Logging
25
+ logging_steps: 10
26
+ eval_steps: 50
27
+ save_steps: 100
28
+ save_total_limit: 3
29
+
30
+ lora:
31
+ enabled: true
32
+ r: 64
33
+ lora_alpha: 128
34
+ lora_dropout: 0.05
35
+ target_modules:
36
+ - "q_proj"
37
+ - "k_proj"
38
+ - "v_proj"
39
+ - "o_proj"
40
+ - "gate_proj"
41
+ - "up_proj"
42
+ - "down_proj"
43
+ bias: "none"
44
+ task_type: "CAUSAL_LM"
45
+
46
+ generation:
47
+ max_new_tokens: 4096
48
+ temperature: 0.7
49
+ top_p: 0.95
50
+ do_sample: true
51
+
52
+ wandb:
53
+ project: "orbgen-training"
54
+ entity: "orbital-ai"
55
+ run_name: "orbgen-1.5b-sft"
evaluate.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ OrbGen Evaluation Script
4
+
5
+ Evaluates a trained model on the test set with Orbital validation metrics.
6
+
7
+ Usage:
8
+ python evaluate.py --checkpoint ./orbgen-1.5b/final
9
+ python evaluate.py --checkpoint ./orbgen-1.5b/final --use_validator
10
+ """
11
+
12
+ import os
13
+ import json
14
+ import fire
15
+ import torch
16
+ import subprocess
17
+ import tempfile
18
+ from pathlib import Path
19
+ from datasets import load_dataset
20
+ from transformers import AutoModelForCausalLM, AutoTokenizer
21
+ from peft import PeftModel
22
+ from tqdm import tqdm
23
+
24
+
25
+ def validate_schema(schema_json: str) -> tuple[bool, list[str]]:
26
+ """Validate schema using orbital CLI."""
27
+ # Check valid JSON first
28
+ try:
29
+ json.loads(schema_json)
30
+ except json.JSONDecodeError as e:
31
+ return False, [f"Invalid JSON: {e}"]
32
+
33
+ # Write to temp file and validate
34
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.orb', delete=False) as f:
35
+ f.write(schema_json)
36
+ temp_path = f.name
37
+
38
+ try:
39
+ result = subprocess.run(
40
+ ['orbital', 'validate', temp_path],
41
+ capture_output=True,
42
+ text=True,
43
+ timeout=30,
44
+ cwd=os.path.expanduser('~/kflow.ai.builder/builder')
45
+ )
46
+
47
+ if result.returncode == 0 or 'Schema is valid' in result.stdout:
48
+ return True, []
49
+ else:
50
+ errors = [line for line in result.stderr.split('\n') if line.strip()]
51
+ return False, errors[:5]
52
+ except subprocess.TimeoutExpired:
53
+ return False, ["Validation timeout"]
54
+ except FileNotFoundError:
55
+ return False, ["Orbital CLI not found"]
56
+ except Exception as e:
57
+ return False, [f"Validation error: {e}"]
58
+ finally:
59
+ Path(temp_path).unlink(missing_ok=True)
60
+
61
+
62
+ def extract_completion(generated_text: str) -> str:
63
+ """Extract the completion from generated text."""
64
+ # Try to find assistant response
65
+ if '<|im_start|>assistant' in generated_text:
66
+ parts = generated_text.split('<|im_start|>assistant')
67
+ if len(parts) > 1:
68
+ completion = parts[-1]
69
+ if '<|im_end|>' in completion:
70
+ completion = completion.split('<|im_end|>')[0]
71
+ return completion.strip()
72
+
73
+ # Try to find JSON object
74
+ start = generated_text.find('{')
75
+ if start != -1:
76
+ # Find matching closing brace
77
+ depth = 0
78
+ for i, char in enumerate(generated_text[start:]):
79
+ if char == '{':
80
+ depth += 1
81
+ elif char == '}':
82
+ depth -= 1
83
+ if depth == 0:
84
+ return generated_text[start:start + i + 1]
85
+
86
+ return generated_text
87
+
88
+
89
+ def main(
90
+ checkpoint: str = "./orbgen-1.5b/final",
91
+ dataset: str = "orbital-ai/orbital-schemas",
92
+ split: str = "test",
93
+ use_validator: bool = False,
94
+ max_samples: int = -1,
95
+ output_file: str = "evaluation_results.json",
96
+ ):
97
+ """Evaluate model on test set."""
98
+
99
+ print("=" * 60)
100
+ print("OrbGen Evaluation")
101
+ print("=" * 60)
102
+ print(f"Checkpoint: {checkpoint}")
103
+ print(f"Dataset: {dataset}")
104
+ print(f"Split: {split}")
105
+ print(f"Use Validator: {use_validator}")
106
+ print("=" * 60)
107
+
108
+ # Load tokenizer and model
109
+ print("\nLoading model...")
110
+ tokenizer = AutoTokenizer.from_pretrained(checkpoint, trust_remote_code=True)
111
+ tokenizer.pad_token = tokenizer.eos_token
112
+
113
+ model = AutoModelForCausalLM.from_pretrained(
114
+ checkpoint,
115
+ torch_dtype=torch.bfloat16,
116
+ device_map="auto",
117
+ trust_remote_code=True,
118
+ )
119
+ model.eval()
120
+
121
+ # Load dataset
122
+ print("Loading dataset...")
123
+ ds = load_dataset(dataset)
124
+ test_data = ds[split]
125
+
126
+ if max_samples > 0:
127
+ test_data = test_data.select(range(min(max_samples, len(test_data))))
128
+
129
+ print(f"Evaluating on {len(test_data)} examples...")
130
+
131
+ # Metrics
132
+ metrics = {
133
+ 'total': len(test_data),
134
+ 'valid_json': 0,
135
+ 'valid_schema': 0,
136
+ 'generation_errors': 0,
137
+ }
138
+ results = []
139
+
140
+ system_prompt = """You are OrbGen, a specialized AI that generates valid Orbital schemas (.orb files) from natural language descriptions.
141
+
142
+ Rules:
143
+ 1. Output ONLY valid JSON - no explanations, no markdown code blocks
144
+ 2. Every schema must have: name, version, orbitals array
145
+ 3. Each orbital must have: name, entity, traits, pages
146
+ 4. Each entity must have: name, collection (or runtime/singleton), fields
147
+ 5. Each trait must have: name, category (interaction/integration), linkedEntity, stateMachine
148
+ 6. State machines must have: states (with one isInitial:true), events, transitions
149
+ 7. Use S-expression arrays for effects: ["set", "field", "value"], ["emit", "EVENT", {}], ["render-ui", "slot", {...}]
150
+ 8. Pages must have: name, path, entity, traits"""
151
+
152
+ for i, example in enumerate(tqdm(test_data)):
153
+ prompt = example['prompt']
154
+ expected = example['completion']
155
+
156
+ # Format input
157
+ input_text = f"""<|im_start|>system
158
+ {system_prompt}
159
+ <|im_end|>
160
+ <|im_start|>user
161
+ {prompt}
162
+ <|im_end|>
163
+ <|im_start|>assistant
164
+ """
165
+
166
+ try:
167
+ # Generate
168
+ inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
169
+
170
+ with torch.no_grad():
171
+ outputs = model.generate(
172
+ **inputs,
173
+ max_new_tokens=4096,
174
+ temperature=0.7,
175
+ top_p=0.95,
176
+ do_sample=True,
177
+ pad_token_id=tokenizer.eos_token_id,
178
+ )
179
+
180
+ generated = tokenizer.decode(outputs[0], skip_special_tokens=False)
181
+ completion = extract_completion(generated)
182
+
183
+ # Check valid JSON
184
+ is_valid_json = False
185
+ is_valid_schema = False
186
+ errors = []
187
+
188
+ try:
189
+ json.loads(completion)
190
+ is_valid_json = True
191
+ metrics['valid_json'] += 1
192
+
193
+ # Check valid schema
194
+ if use_validator:
195
+ is_valid_schema, errors = validate_schema(completion)
196
+ if is_valid_schema:
197
+ metrics['valid_schema'] += 1
198
+ else:
199
+ # Basic structural check
200
+ parsed = json.loads(completion)
201
+ if 'name' in parsed and 'orbitals' in parsed:
202
+ is_valid_schema = True
203
+ metrics['valid_schema'] += 1
204
+
205
+ except json.JSONDecodeError as e:
206
+ errors = [f"JSON error: {e}"]
207
+
208
+ results.append({
209
+ 'prompt': prompt,
210
+ 'expected': expected[:500] + '...' if len(expected) > 500 else expected,
211
+ 'generated': completion[:500] + '...' if len(completion) > 500 else completion,
212
+ 'valid_json': is_valid_json,
213
+ 'valid_schema': is_valid_schema,
214
+ 'errors': errors,
215
+ })
216
+
217
+ except Exception as e:
218
+ metrics['generation_errors'] += 1
219
+ results.append({
220
+ 'prompt': prompt,
221
+ 'error': str(e),
222
+ 'valid_json': False,
223
+ 'valid_schema': False,
224
+ })
225
+
226
+ # Calculate percentages
227
+ metrics['valid_json_pct'] = metrics['valid_json'] / metrics['total'] * 100
228
+ metrics['valid_schema_pct'] = metrics['valid_schema'] / metrics['total'] * 100
229
+
230
+ # Print results
231
+ print("\n" + "=" * 60)
232
+ print("Results")
233
+ print("=" * 60)
234
+ print(f"Total examples: {metrics['total']}")
235
+ print(f"Valid JSON: {metrics['valid_json']} ({metrics['valid_json_pct']:.1f}%)")
236
+ print(f"Valid Schema: {metrics['valid_schema']} ({metrics['valid_schema_pct']:.1f}%)")
237
+ print(f"Generation errors: {metrics['generation_errors']}")
238
+
239
+ # Save results
240
+ output = {
241
+ 'metrics': metrics,
242
+ 'results': results,
243
+ }
244
+
245
+ with open(output_file, 'w') as f:
246
+ json.dump(output, f, indent=2)
247
+
248
+ print(f"\nResults saved to: {output_file}")
249
+
250
+ return metrics
251
+
252
+
253
+ if __name__ == "__main__":
254
+ fire.Fire(main)
generate.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ OrbGen Inference Script
4
+
5
+ Generate Orbital schemas from natural language prompts.
6
+
7
+ Usage:
8
+ python generate.py --prompt "Create a task management app"
9
+ python generate.py --prompt "..." --checkpoint ./orbgen-1.5b/final
10
+ python generate.py --interactive
11
+ """
12
+
13
+ import os
14
+ import json
15
+ import fire
16
+ import torch
17
+ from transformers import AutoModelForCausalLM, AutoTokenizer
18
+
19
+
20
+ SYSTEM_PROMPT = """You are OrbGen, a specialized AI that generates valid Orbital schemas (.orb files) from natural language descriptions.
21
+
22
+ Rules:
23
+ 1. Output ONLY valid JSON - no explanations, no markdown code blocks
24
+ 2. Every schema must have: name, version, orbitals array
25
+ 3. Each orbital must have: name, entity, traits, pages
26
+ 4. Each entity must have: name, collection (or runtime/singleton), fields
27
+ 5. Each trait must have: name, category (interaction/integration), linkedEntity, stateMachine
28
+ 6. State machines must have: states (with one isInitial:true), events, transitions
29
+ 7. Use S-expression arrays for effects: ["set", "field", "value"], ["emit", "EVENT", {}], ["render-ui", "slot", {...}]
30
+ 8. Pages must have: name, path, entity, traits"""
31
+
32
+
33
+ class OrbGen:
34
+ """OrbGen schema generator."""
35
+
36
+ def __init__(
37
+ self,
38
+ checkpoint: str = "orbital-ai/orbgen-1.5b",
39
+ device: str = "auto",
40
+ ):
41
+ """Initialize the generator."""
42
+ print(f"Loading model from {checkpoint}...")
43
+
44
+ self.tokenizer = AutoTokenizer.from_pretrained(
45
+ checkpoint,
46
+ trust_remote_code=True,
47
+ )
48
+ self.tokenizer.pad_token = self.tokenizer.eos_token
49
+
50
+ self.model = AutoModelForCausalLM.from_pretrained(
51
+ checkpoint,
52
+ torch_dtype=torch.bfloat16,
53
+ device_map=device,
54
+ trust_remote_code=True,
55
+ )
56
+ self.model.eval()
57
+
58
+ print("Model loaded!")
59
+
60
+ def generate(
61
+ self,
62
+ prompt: str,
63
+ temperature: float = 0.7,
64
+ top_p: float = 0.95,
65
+ max_new_tokens: int = 4096,
66
+ ) -> str:
67
+ """Generate a schema from a prompt."""
68
+
69
+ input_text = f"""<|im_start|>system
70
+ {SYSTEM_PROMPT}
71
+ <|im_end|>
72
+ <|im_start|>user
73
+ {prompt}
74
+ <|im_end|>
75
+ <|im_start|>assistant
76
+ """
77
+
78
+ inputs = self.tokenizer(input_text, return_tensors="pt").to(self.model.device)
79
+
80
+ with torch.no_grad():
81
+ outputs = self.model.generate(
82
+ **inputs,
83
+ max_new_tokens=max_new_tokens,
84
+ temperature=temperature,
85
+ top_p=top_p,
86
+ do_sample=True,
87
+ pad_token_id=self.tokenizer.eos_token_id,
88
+ )
89
+
90
+ generated = self.tokenizer.decode(outputs[0], skip_special_tokens=False)
91
+
92
+ # Extract completion
93
+ if '<|im_start|>assistant' in generated:
94
+ parts = generated.split('<|im_start|>assistant')
95
+ if len(parts) > 1:
96
+ completion = parts[-1]
97
+ if '<|im_end|>' in completion:
98
+ completion = completion.split('<|im_end|>')[0]
99
+ return completion.strip()
100
+
101
+ # Try to find JSON
102
+ start = generated.find('{')
103
+ if start != -1:
104
+ depth = 0
105
+ for i, char in enumerate(generated[start:]):
106
+ if char == '{':
107
+ depth += 1
108
+ elif char == '}':
109
+ depth -= 1
110
+ if depth == 0:
111
+ return generated[start:start + i + 1]
112
+
113
+ return generated
114
+
115
+
116
+ def main(
117
+ prompt: str = None,
118
+ checkpoint: str = "orbital-ai/orbgen-1.5b",
119
+ output: str = None,
120
+ temperature: float = 0.7,
121
+ top_p: float = 0.95,
122
+ interactive: bool = False,
123
+ validate: bool = False,
124
+ ):
125
+ """Generate Orbital schemas."""
126
+
127
+ generator = OrbGen(checkpoint=checkpoint)
128
+
129
+ if interactive:
130
+ print("\n" + "=" * 60)
131
+ print("OrbGen Interactive Mode")
132
+ print("=" * 60)
133
+ print("Enter prompts to generate schemas. Type 'quit' to exit.\n")
134
+
135
+ while True:
136
+ try:
137
+ prompt = input("Prompt> ").strip()
138
+ if prompt.lower() in ['quit', 'exit', 'q']:
139
+ break
140
+ if not prompt:
141
+ continue
142
+
143
+ print("\nGenerating...")
144
+ result = generator.generate(prompt, temperature=temperature, top_p=top_p)
145
+
146
+ try:
147
+ parsed = json.loads(result)
148
+ print(json.dumps(parsed, indent=2))
149
+ except json.JSONDecodeError:
150
+ print(result)
151
+
152
+ print()
153
+
154
+ except KeyboardInterrupt:
155
+ print("\nExiting...")
156
+ break
157
+
158
+ elif prompt:
159
+ print(f"\nPrompt: {prompt}\n")
160
+ print("Generating...")
161
+
162
+ result = generator.generate(prompt, temperature=temperature, top_p=top_p)
163
+
164
+ try:
165
+ parsed = json.loads(result)
166
+ formatted = json.dumps(parsed, indent=2)
167
+
168
+ if output:
169
+ with open(output, 'w') as f:
170
+ f.write(formatted)
171
+ print(f"Schema saved to: {output}")
172
+ else:
173
+ print(formatted)
174
+
175
+ # Validate if requested
176
+ if validate:
177
+ import subprocess
178
+ import tempfile
179
+ from pathlib import Path
180
+
181
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.orb', delete=False) as f:
182
+ f.write(formatted)
183
+ temp_path = f.name
184
+
185
+ try:
186
+ result = subprocess.run(
187
+ ['orbital', 'validate', temp_path],
188
+ capture_output=True,
189
+ text=True,
190
+ cwd=os.path.expanduser('~/kflow.ai.builder/builder')
191
+ )
192
+ print("\nValidation:")
193
+ print(result.stdout or result.stderr)
194
+ finally:
195
+ Path(temp_path).unlink(missing_ok=True)
196
+
197
+ except json.JSONDecodeError as e:
198
+ print(f"Warning: Generated invalid JSON: {e}")
199
+ print(result)
200
+
201
+ else:
202
+ print("Usage: python generate.py --prompt 'Your prompt here'")
203
+ print(" python generate.py --interactive")
204
+
205
+
206
+ if __name__ == "__main__":
207
+ fire.Fire(main)
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # OrbGen Training Dependencies
2
+ torch>=2.1.0
3
+ transformers>=4.36.0
4
+ datasets>=2.16.0
5
+ peft>=0.7.0
6
+ trl>=0.7.0
7
+ accelerate>=0.25.0
8
+ bitsandbytes>=0.41.0
9
+ wandb>=0.16.0
10
+ huggingface_hub>=0.20.0
11
+ safetensors>=0.4.0
12
+ sentencepiece>=0.1.99
13
+ protobuf>=4.25.0
14
+ pyyaml>=6.0
15
+ fire>=0.5.0
train.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ OrbGen Training Script
4
+
5
+ Fine-tunes a base model to generate valid Orbital schemas (.orb files).
6
+
7
+ Usage:
8
+ python train.py --config config.yaml
9
+ python train.py --config config.yaml --debug --max_steps 100
10
+ """
11
+
12
+ import os
13
+ import yaml
14
+ import fire
15
+ import torch
16
+ from datasets import load_dataset
17
+ from transformers import (
18
+ AutoModelForCausalLM,
19
+ AutoTokenizer,
20
+ TrainingArguments,
21
+ DataCollatorForSeq2Seq,
22
+ )
23
+ from peft import LoraConfig, get_peft_model, TaskType, prepare_model_for_kbit_training
24
+ from trl import SFTTrainer, SFTConfig
25
+ import wandb
26
+
27
+
28
+ def load_config(config_path: str) -> dict:
29
+ """Load configuration from YAML file."""
30
+ with open(config_path, 'r') as f:
31
+ return yaml.safe_load(f)
32
+
33
+
34
+ def format_example(example: dict, tokenizer) -> str:
35
+ """Format a single training example as a chat conversation."""
36
+ system_prompt = """You are OrbGen, a specialized AI that generates valid Orbital schemas (.orb files) from natural language descriptions.
37
+
38
+ Rules:
39
+ 1. Output ONLY valid JSON - no explanations, no markdown code blocks
40
+ 2. Every schema must have: name, version, orbitals array
41
+ 3. Each orbital must have: name, entity, traits, pages
42
+ 4. Each entity must have: name, collection (or runtime/singleton), fields
43
+ 5. Each trait must have: name, category (interaction/integration), linkedEntity, stateMachine
44
+ 6. State machines must have: states (with one isInitial:true), events, transitions
45
+ 7. Use S-expression arrays for effects: ["set", "field", "value"], ["emit", "EVENT", {}], ["render-ui", "slot", {...}]
46
+ 8. Pages must have: name, path, entity, traits"""
47
+
48
+ return f"""<|im_start|>system
49
+ {system_prompt}
50
+ <|im_end|>
51
+ <|im_start|>user
52
+ {example['prompt']}
53
+ <|im_end|>
54
+ <|im_start|>assistant
55
+ {example['completion']}
56
+ <|im_end|>"""
57
+
58
+
59
+ def main(
60
+ config: str = "config.yaml",
61
+ debug: bool = False,
62
+ max_steps: int = -1,
63
+ resume_from_checkpoint: str = None,
64
+ ):
65
+ """Main training function."""
66
+
67
+ # Load configuration
68
+ cfg = load_config(config)
69
+
70
+ print("=" * 60)
71
+ print("OrbGen Training")
72
+ print("=" * 60)
73
+ print(f"Base model: {cfg['model']['base_model']}")
74
+ print(f"Output dir: {cfg['model']['output_dir']}")
75
+ print(f"Debug mode: {debug}")
76
+ print("=" * 60)
77
+
78
+ # Initialize wandb
79
+ if not debug:
80
+ wandb.init(
81
+ project=cfg['wandb']['project'],
82
+ entity=cfg['wandb'].get('entity'),
83
+ name=cfg['wandb']['run_name'],
84
+ config=cfg,
85
+ )
86
+
87
+ # Load tokenizer
88
+ print("\nLoading tokenizer...")
89
+ tokenizer = AutoTokenizer.from_pretrained(
90
+ cfg['model']['base_model'],
91
+ trust_remote_code=True,
92
+ )
93
+ tokenizer.pad_token = tokenizer.eos_token
94
+ tokenizer.padding_side = "right"
95
+
96
+ # Load model
97
+ print("Loading model...")
98
+ model = AutoModelForCausalLM.from_pretrained(
99
+ cfg['model']['base_model'],
100
+ torch_dtype=torch.bfloat16,
101
+ device_map="auto",
102
+ trust_remote_code=True,
103
+ )
104
+
105
+ # Prepare model for training
106
+ model.config.use_cache = False
107
+ model.enable_input_require_grads()
108
+
109
+ # Configure LoRA
110
+ if cfg['lora']['enabled']:
111
+ print("Configuring LoRA...")
112
+ lora_config = LoraConfig(
113
+ r=cfg['lora']['r'],
114
+ lora_alpha=cfg['lora']['lora_alpha'],
115
+ lora_dropout=cfg['lora']['lora_dropout'],
116
+ target_modules=cfg['lora']['target_modules'],
117
+ bias=cfg['lora']['bias'],
118
+ task_type=TaskType.CAUSAL_LM,
119
+ )
120
+ model = get_peft_model(model, lora_config)
121
+ model.print_trainable_parameters()
122
+
123
+ # Load dataset
124
+ print("\nLoading dataset...")
125
+ dataset = load_dataset(cfg['data']['dataset'])
126
+
127
+ train_dataset = dataset[cfg['data']['train_split']]
128
+ eval_dataset = dataset[cfg['data']['eval_split']]
129
+
130
+ print(f"Train examples: {len(train_dataset)}")
131
+ print(f"Eval examples: {len(eval_dataset)}")
132
+
133
+ # Format dataset
134
+ def format_dataset(examples):
135
+ texts = []
136
+ for i in range(len(examples['prompt'])):
137
+ example = {
138
+ 'prompt': examples['prompt'][i],
139
+ 'completion': examples['completion'][i],
140
+ }
141
+ texts.append(format_example(example, tokenizer))
142
+ return {'text': texts}
143
+
144
+ train_dataset = train_dataset.map(
145
+ format_dataset,
146
+ batched=True,
147
+ remove_columns=train_dataset.column_names,
148
+ )
149
+
150
+ eval_dataset = eval_dataset.map(
151
+ format_dataset,
152
+ batched=True,
153
+ remove_columns=eval_dataset.column_names,
154
+ )
155
+
156
+ # Training arguments
157
+ training_args = SFTConfig(
158
+ output_dir=cfg['model']['output_dir'],
159
+ num_train_epochs=cfg['training']['num_epochs'] if not debug else 1,
160
+ per_device_train_batch_size=cfg['training']['per_device_train_batch_size'],
161
+ per_device_eval_batch_size=cfg['training']['per_device_eval_batch_size'],
162
+ gradient_accumulation_steps=cfg['training']['gradient_accumulation_steps'],
163
+ learning_rate=cfg['training']['learning_rate'],
164
+ warmup_ratio=cfg['training']['warmup_ratio'],
165
+ weight_decay=cfg['training']['weight_decay'],
166
+ max_grad_norm=cfg['training']['max_grad_norm'],
167
+ logging_steps=cfg['training']['logging_steps'],
168
+ eval_strategy="steps",
169
+ eval_steps=cfg['training']['eval_steps'],
170
+ save_steps=cfg['training']['save_steps'],
171
+ save_total_limit=cfg['training']['save_total_limit'],
172
+ load_best_model_at_end=True,
173
+ metric_for_best_model="eval_loss",
174
+ greater_is_better=False,
175
+ bf16=True,
176
+ gradient_checkpointing=True,
177
+ gradient_checkpointing_kwargs={"use_reentrant": False},
178
+ max_seq_length=cfg['model']['max_seq_length'],
179
+ dataset_text_field="text",
180
+ report_to="wandb" if not debug else "none",
181
+ max_steps=max_steps if max_steps > 0 else -1,
182
+ )
183
+
184
+ # Create trainer
185
+ trainer = SFTTrainer(
186
+ model=model,
187
+ args=training_args,
188
+ train_dataset=train_dataset,
189
+ eval_dataset=eval_dataset,
190
+ tokenizer=tokenizer,
191
+ )
192
+
193
+ # Train
194
+ print("\nStarting training...")
195
+ trainer.train(resume_from_checkpoint=resume_from_checkpoint)
196
+
197
+ # Save final model
198
+ print("\nSaving model...")
199
+ trainer.save_model(f"{cfg['model']['output_dir']}/final")
200
+ tokenizer.save_pretrained(f"{cfg['model']['output_dir']}/final")
201
+
202
+ # Finish wandb
203
+ if not debug:
204
+ wandb.finish()
205
+
206
+ print("\nTraining complete!")
207
+ print(f"Model saved to: {cfg['model']['output_dir']}/final")
208
+
209
+
210
+ if __name__ == "__main__":
211
+ fire.Fire(main)
utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # OrbGen Training Utilities