File size: 5,975 Bytes
3774975
ef5f450
 
 
 
 
3774975
 
ef5f450
 
d68ea87
ef5f450
 
 
 
 
 
 
 
c8b552c
ef5f450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3774975
ef5f450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3774975
ef5f450
 
 
 
 
 
 
 
3774975
 
 
ef5f450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Irminsul

> Fine-tuned Llama 3.1 8B · QLoRA · Pinecone RAG · FastAPI · Azure Container Apps

A full end-to-end LLMOps serving stack — from a QLoRA fine-tuned model running in 4-bit NF4 on consumer hardware, through a retrieval-augmented generation pipeline, to a containerized API deployed on Azure. Built to be production-shaped, not just a demo.

**[→ Live Demo](https://mukulray1603.github.io/Irminsul/demo.html)**

---

## About Irminsul

Most LLM projects stop at inference. This one goes further:

- **Fine-tuned model** — Llama 3.1 8B fine-tuned with QLoRA (rank 16, lr 2e-4) on a custom dataset, merged and served locally in 4-bit NF4 quantization on an RTX 3060 6GB
- **RAG pipeline** — Documents ingested, chunked, embedded with `sentence-transformers/all-MiniLM-L6-v2` (fully local, zero API cost), and stored in Pinecone. Retrieval is semantic, top-k configurable at query time
- **Serving layer** — FastAPI with async lifespan model loading, typed Pydantic request/response models, CORS, health check, and a clean browser UI served from the same process
- **Containerized** — Dockerfile built for slim Python 3.12, model loaded at runtime via env-configurable path (not baked in)
- **Cloud-ready** — One-shot Azure deployment via ACR + Container Apps, with Pinecone key injected as a secret
- **Domain knowledge** — RAG corpus built around Genshin Impact lore, character builds, and elemental mechanics, serving as a rich real-world knowledge base for retrieval evaluation

---

## Architecture

```
User query


FastAPI  /generate

    ├── Embed query (sentence-transformers, local)
    │       │
    │       ▼
    │   Pinecone — semantic search → top-k chunks
    │       │
    ▼       ▼
  LangChain RetrievalQA


Llama 3.1 8B (QLoRA fine-tuned, 4-bit NF4)


Grounded answer + source attribution
```

---

## Stack

| Layer | Technology |
|---|---|
| Base model | Llama 3.1 8B Instruct |
| Fine-tuning | QLoRA via PEFT (r=16, α=32, lr=2e-4) |
| Quantization | BitsAndBytes 4-bit NF4, bfloat16 compute |
| Embeddings | sentence-transformers/all-MiniLM-L6-v2 |
| Vector DB | Pinecone (serverless, cosine similarity) |
| RAG chain | LangChain RetrievalQA |
| Serving | FastAPI + Uvicorn |
| Containerization | Docker (python:3.12-slim) |
| Cloud | Azure Container Apps + ACR |

---

## Quickstart

```bash
# 1. Clone and set up environment
git clone https://github.com/MukulRay1603/Irminsul.git
cd Irminsul
python -m venv venv && source venv/bin/activate  # Windows: venv\Scripts\activate
pip install -r requirements.txt

# 2. Configure environment
cp .env.example .env
# Fill in PINECONE_API_KEY in .env

# 3. Add your fine-tuned model
# Place merged model at: ./models/merged/exp2_lr2e-4_r16
# Or update MODEL_PATH in .env to point to your model

# 4. Ingest documents
python ingest.py --dir ./docs --chunk-size 300 --chunk-overlap 40

# 5. Start the server
uvicorn main:app --reload --port 8000

# UI available at http://localhost:8000
# API docs at http://localhost:8000/docs
```

---

## API

| Method | Endpoint | Description |
|---|---|---|
| `GET` | `/` | Browser UI |
| `GET` | `/health` | Model load status |
| `POST` | `/generate` | RAG query → grounded answer |
| `POST` | `/ingest` | Ingest docs from local directory |

**Example:**

```bash
curl -X POST http://localhost:8000/generate \
  -H "Content-Type: application/json" \
  -d '{"query": "What weapons should Hu Tao use on a budget?", "top_k": 3}'
```

```json
{
  "answer": "For Hu Tao on a budget, Dragon's Bane is the strongest F2P option — it scales with Elemental Mastery and deals significant bonus damage on vaporized hits. White Tassel is the best 3-star alternative for pure Normal Attack scaling.",
  "sources": ["docs/character_builds.md"],
  "latency_ms": 4821.3
}
```

---

## Memory profile (RTX 3060 6GB)

| Component | VRAM |
|---|---|
| Llama 3.1 8B @ 4-bit NF4 | ~4.5 GB |
| all-MiniLM-L6-v2 embedder | ~90 MB |
| Inference headroom | ~1.2 GB |

Running the embedder on CPU frees ~90MB if needed — set `device_map="cpu"` in `rag.py`.

---

## Deploy to Azure

```bash
export PINECONE_API_KEY=your_key
chmod +x deploy_azure.sh
./deploy_azure.sh
```

The script provisions a resource group, builds and pushes the image via ACR Tasks (no local Docker build needed), creates a Container Apps environment, and deploys with the Pinecone key injected as a secret. Prints the live HTTPS endpoint on completion.

**Model in Azure:** The merged model (~16GB) isn't baked into the image. Recommended approach: mount from Azure Blob Storage as a volume for cheapest cold start on student credits.

---

## Project structure

```
Irminsul/
├── main.py           # FastAPI app — endpoints, lifespan, CORS
├── rag.py            # Model loading, 4-bit config, LangChain RAG chain
├── embedder.py       # sentence-transformers singleton wrapper
├── ingest.py         # Doc loader → chunker → Pinecone upsert
├── index.html        # Browser UI (dark theme, query history, source display)
├── Dockerfile
├── deploy_azure.sh   # One-shot Azure Container Apps deploy
├── requirements.txt
├── .env.example
└── docs/             # Corpus + GitHub Pages demo
    └── demo.html
```

---

## What's next

- [ ] Swap naive word chunker for `MarkdownHeaderTextSplitter` for better retrieval precision
- [ ] Add metadata filtering to Pinecone queries (filter by character, content type)
- [ ] Streaming response via SSE for lower perceived latency
- [ ] Expand corpus — per-character deep dives with stat thresholds and rotation guides
- [ ] CI/CD pipeline — GitHub Actions → ACR build → Container Apps deploy on push

---

Built while learning the full MLOps lifecycle — fine-tuning, quantization, retrieval, serving, and cloud deployment — on consumer hardware. Every component chosen deliberately, not for hype.