File size: 7,326 Bytes
dcc24f8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
"""
GGUF Conversion Script.

Converts the PyTorch model to GGUF format for llama.cpp compatibility.
This enables deployment on any platform (Linux, Windows, Mac) with CPU or GPU.

Author: Ranjit Behera
"""

import subprocess
import argparse
import sys
from pathlib import Path
import shutil
import tempfile
import os


def check_llama_cpp():
    """Check if llama.cpp is available, download if needed."""
    llama_cpp_path = Path("tools/llama.cpp")
    
    if not llama_cpp_path.exists():
        print("πŸ“₯ llama.cpp not found. Cloning repository...")
        llama_cpp_path.parent.mkdir(parents=True, exist_ok=True)
        
        subprocess.run([
            "git", "clone", "--depth", "1",
            "https://github.com/ggerganov/llama.cpp.git",
            str(llama_cpp_path)
        ], check=True)
        
        print("βœ… llama.cpp cloned successfully")
    
    return llama_cpp_path


def convert_to_gguf(model_path: Path, output_path: Path, quantization: str = "q4_k_m"):
    """
    Convert PyTorch/Safetensors model to GGUF format.
    
    Args:
        model_path: Path to the PyTorch model directory
        output_path: Output path for GGUF file
        quantization: Quantization type (q4_k_m, q5_k_m, q8_0, f16, f32)
    """
    llama_cpp = check_llama_cpp()
    convert_script = llama_cpp / "convert_hf_to_gguf.py"
    
    if not convert_script.exists():
        # Try alternative script name
        convert_script = llama_cpp / "convert-hf-to-gguf.py"
    
    if not convert_script.exists():
        print("❌ Conversion script not found in llama.cpp")
        print("   Trying pip-installed llama-cpp-python converter...")
        return convert_with_pip_package(model_path, output_path, quantization)
    
    # Step 1: Convert to GGUF (F16)
    print(f"πŸ”„ Converting {model_path} to GGUF (F16)...")
    
    f16_output = output_path.parent / f"{output_path.stem}-f16.gguf"
    
    cmd = [
        sys.executable, str(convert_script),
        str(model_path),
        "--outfile", str(f16_output),
        "--outtype", "f16"
    ]
    
    try:
        subprocess.run(cmd, check=True)
        print(f"βœ… F16 GGUF created: {f16_output}")
    except subprocess.CalledProcessError as e:
        print(f"❌ Conversion failed: {e}")
        return None
    
    # Step 2: Quantize if needed
    if quantization != "f16":
        print(f"πŸ”„ Quantizing to {quantization}...")
        
        quantize_bin = llama_cpp / "quantize"
        if not quantize_bin.exists():
            quantize_bin = llama_cpp / "build" / "bin" / "quantize"
        
        if not quantize_bin.exists():
            print("⚠️ Quantize binary not found. Using F16 output.")
            shutil.move(f16_output, output_path)
            return output_path
        
        cmd = [str(quantize_bin), str(f16_output), str(output_path), quantization]
        
        try:
            subprocess.run(cmd, check=True)
            print(f"βœ… Quantized GGUF created: {output_path}")
            f16_output.unlink()  # Remove intermediate F16 file
        except subprocess.CalledProcessError as e:
            print(f"⚠️ Quantization failed, using F16: {e}")
            shutil.move(f16_output, output_path)
    else:
        shutil.move(f16_output, output_path)
    
    return output_path


def convert_with_pip_package(model_path: Path, output_path: Path, quantization: str):
    """
    Alternative conversion using transformers + gguf library.
    """
    try:
        from transformers import AutoModelForCausalLM, AutoTokenizer
        import torch
    except ImportError:
        print("❌ transformers not installed. Run: pip install transformers torch")
        return None
    
    print("πŸ”„ Loading model for conversion...")
    
    # Check if gguf conversion is available via transformers
    try:
        # Try using the convert script from transformers
        cmd = [
            sys.executable, "-m", "transformers.gguf",
            "--model", str(model_path),
            "--output", str(output_path)
        ]
        subprocess.run(cmd, check=True)
        return output_path
    except Exception as e:
        print(f"⚠️ Direct conversion not available: {e}")
        print("\nπŸ“‹ Manual GGUF conversion instructions:")
        print("=" * 50)
        print("1. Clone llama.cpp:")
        print("   git clone https://github.com/ggerganov/llama.cpp")
        print("   cd llama.cpp && make")
        print("")
        print("2. Convert the model:")
        print(f"   python convert_hf_to_gguf.py {model_path} --outfile {output_path}")
        print("")
        print("3. (Optional) Quantize:")
        print(f"   ./quantize {output_path} {output_path.stem}-q4_k_m.gguf q4_k_m")
        print("=" * 50)
        return None


def create_gguf_readme(output_dir: Path):
    """Create a README for GGUF usage."""
    readme = """# GGUF Model for llama.cpp

## Quick Start

### Using llama.cpp CLI
```bash
# Clone and build llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp && make

# Run inference
./main -m finance-extractor-v8-q4_k_m.gguf -p "Extract financial entities from: Rs.500 debited from A/c 1234 on 01-01-25"
```

### Using Python (llama-cpp-python)
```bash
pip install llama-cpp-python
```

```python
from llama_cpp import Llama

llm = Llama(model_path="finance-extractor-v8-q4_k_m.gguf")

output = llm(
    "Extract financial entities from: Rs.500 debited from A/c 1234 on 01-01-25",
    max_tokens=200,
    stop=["\\n\\n"]
)

print(output["choices"][0]["text"])
```

## Quantization Variants

| File | Size | Quality | Speed |
|------|------|---------|-------|
| `*-f16.gguf` | ~7.6GB | Highest | Slowest |
| `*-q8_0.gguf` | ~4GB | Very High | Fast |
| `*-q4_k_m.gguf` | ~2GB | Good | Fastest |

## Compatibility

- βœ… Linux (CPU, NVIDIA GPU, AMD GPU)
- βœ… Windows (CPU, NVIDIA GPU)
- βœ… macOS (CPU, Metal)
- βœ… Any llama.cpp compatible tool
"""
    
    with open(output_dir / "GGUF_README.md", "w") as f:
        f.write(readme)


def main():
    parser = argparse.ArgumentParser(description="Convert PyTorch model to GGUF")
    parser.add_argument(
        "--model", 
        default="models/released/finance-extractor-v8-pytorch",
        help="Path to PyTorch model directory"
    )
    parser.add_argument(
        "--output",
        default="models/released/finance-extractor-v8-q4_k_m.gguf",
        help="Output GGUF file path"
    )
    parser.add_argument(
        "--quantization",
        default="q4_k_m",
        choices=["f16", "q8_0", "q5_k_m", "q4_k_m", "q4_0"],
        help="Quantization type"
    )
    
    args = parser.parse_args()
    
    model_path = Path(args.model)
    output_path = Path(args.output)
    
    if not model_path.exists():
        print(f"❌ Model not found: {model_path}")
        sys.exit(1)
    
    output_path.parent.mkdir(parents=True, exist_ok=True)
    
    result = convert_to_gguf(model_path, output_path, args.quantization)
    
    if result:
        print(f"\nπŸŽ‰ GGUF conversion complete!")
        print(f"   Output: {result}")
        print(f"   Size: {result.stat().st_size / (1024**3):.2f} GB")
        create_gguf_readme(result.parent)
    else:
        print("\n⚠️ GGUF conversion requires manual steps (see instructions above)")


if __name__ == "__main__":
    main()