Essay-Grader commited on
Commit
6d3d699
·
1 Parent(s): 8963da2

Optimized api

Browse files
Dockerfile CHANGED
@@ -1,32 +1,67 @@
1
- FROM python:3.9-slim
2
 
3
- WORKDIR /code
 
4
 
5
- # Hugging Face Space requirements
6
- ENV HF_HOME=/tmp/cache \
7
- TRANSFORMERS_CACHE=/tmp/cache \
8
- SENTENCE_TRANSFORMERS_HOME=/tmp/cache \
9
- PATH="/home/appuser/.local/bin:${PATH}"
10
 
11
- # System dependencies
12
  RUN apt-get update && apt-get install -y --no-install-recommends \
13
- build-essential \
14
- git \
 
15
  && rm -rf /var/lib/apt/lists/*
16
 
17
- # Create cache directory and non-root user
18
- RUN mkdir -p ${HF_HOME} && chmod 777 ${HF_HOME} && \
19
- useradd -m appuser && chown -R appuser /code ${HF_HOME}
20
 
21
- USER appuser
 
 
 
 
 
 
22
 
23
- # Install Python dependencies
24
- COPY --chown=appuser:appuser requirements.txt .
25
- RUN pip install --no-cache-dir --upgrade pip && \
26
- pip install --no-cache-dir -r requirements.txt
27
 
28
- # Copy application code
29
- COPY --chown=appuser:appuser app.py .
30
 
31
- # Hugging Face Space-specific CMD
32
- CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
+ # Use minimal Python image with Intel MKL optimizations
3
+ FROM python:3.9-slim-bullseye
4
 
5
+ # Configure environment for CPU optimization
6
+ ENV DEBIAN_FRONTEND=noninteractive \
7
+ OMP_NUM_THREADS=4 \
8
+ PORT=7860 \
9
+ MAX_WORKERS=2
10
 
11
+ # Install system dependencies
12
  RUN apt-get update && apt-get install -y --no-install-recommends \
13
+ gcc \
14
+ libgl1 \
15
+ poppler-utils \
16
  && rm -rf /var/lib/apt/lists/*
17
 
18
+ WORKDIR /app
 
 
19
 
20
+ # Install Python dependencies with CPU-optimized versions
21
+ COPY requirements.txt .
22
+ RUN pip install --no-cache-dir -U pip && \
23
+ pip install --no-cache-dir \
24
+ -r requirements.txt \
25
+ --timeout 600 \
26
+ --extra-index-url https://download.pytorch.org/whl/cpu
27
 
28
+ # Copy application files
29
+ COPY . .
 
 
30
 
31
+ # Start the server
32
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "2"]
33
 
34
+
35
+
36
+ # FROM python:3.9-slim
37
+
38
+ # WORKDIR /code
39
+
40
+ # # Hugging Face Space requirements
41
+ # ENV HF_HOME=/tmp/cache \
42
+ # TRANSFORMERS_CACHE=/tmp/cache \
43
+ # SENTENCE_TRANSFORMERS_HOME=/tmp/cache \
44
+ # PATH="/home/appuser/.local/bin:${PATH}"
45
+
46
+ # # System dependencies
47
+ # RUN apt-get update && apt-get install -y --no-install-recommends \
48
+ # build-essential \
49
+ # git \
50
+ # && rm -rf /var/lib/apt/lists/*
51
+
52
+ # # Create cache directory and non-root user
53
+ # RUN mkdir -p ${HF_HOME} && chmod 777 ${HF_HOME} && \
54
+ # useradd -m appuser && chown -R appuser /code ${HF_HOME}
55
+
56
+ # USER appuser
57
+
58
+ # # Install Python dependencies
59
+ # COPY --chown=appuser:appuser requirements.txt .
60
+ # RUN pip install --no-cache-dir --upgrade pip && \
61
+ # pip install --no-cache-dir -r requirements.txt
62
+
63
+ # # Copy application code
64
+ # COPY --chown=appuser:appuser app.py .
65
+
66
+ # # Hugging Face Space-specific CMD
67
+ # CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,183 +1,112 @@
1
  # app.py: AI Detection and Plagiarism Check API
2
-
3
-
4
- from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
 
 
5
  from fastapi.responses import JSONResponse
 
 
6
  from sentence_transformers import SentenceTransformer
7
- from transformers import AutoModelForSequenceClassification, AutoTokenizer
8
  from PyPDF2 import PdfReader
9
- from sklearn.metrics.pairwise import cosine_similarity
10
- import torch
11
- import os
12
- import numpy as np
13
- import shutil
14
- import uuid
15
  import tempfile
16
- import logging
17
- import time
18
- from typing import Dict, Any
19
-
20
- # Configure logging
21
- logging.basicConfig(
22
- level=logging.INFO,
23
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
24
- )
25
- logger = logging.getLogger(__name__)
26
-
27
- app = FastAPI(
28
- title="Essay Analysis API",
29
- version="1.0.0",
30
- docs_url="/docs",
31
- redoc_url=None
32
- )
33
 
34
  # Configuration
35
- CACHE_DIR = "/tmp/cache"
36
- PLAGIARISM_THRESHOLD = 0.82
37
- MAX_TEXT_LENGTH = 512
38
  MODEL_NAME = "Essay-Grader/roberta-ai-detector-20250401_232702"
39
- SENTENCE_MODEL = "sentence-transformers/all-roberta-large-v1"
40
-
41
- # Global State
42
- model_status = {
43
- "model_loaded": False,
44
- "last_error": None
45
- }
46
 
47
- # Model References
48
- embedder = None
49
- ai_tokenizer = None
50
- ai_model = None
51
 
52
- def initialize_models():
53
- global embedder, ai_tokenizer, ai_model
 
54
 
55
- try:
56
- # Cleanup existing models
57
- if embedder or ai_model:
58
- del embedder, ai_tokenizer, ai_model
59
- torch.cuda.empty_cache()
60
-
61
- # Load models
62
- logger.info("Loading models...")
63
- embedder = SentenceTransformer(SENTENCE_MODEL)
64
- ai_tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
65
- ai_model = AutoModelForSequenceClassification.from_pretrained(
66
- MODEL_NAME,
67
- device_map="auto",
68
- torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32
69
- ).eval()
70
-
71
- # Warmup
72
- test_text = "Model initialization text. " * 50
73
- inputs = ai_tokenizer(test_text, return_tensors="pt", truncation=True)
74
- with torch.no_grad():
75
- ai_model(**inputs.to(ai_model.device))
76
-
77
- model_status.update({"model_loaded": True, "last_error": None})
78
- return True
79
-
80
- except Exception as e:
81
- error_msg = f"Model load failed: {str(e)}"
82
- logger.error(error_msg)
83
- model_status.update({"model_loaded": False, "last_error": error_msg})
84
- return False
85
-
86
- @app.on_event("startup")
87
- async def startup_event():
88
- for _ in range(3):
89
- if initialize_models():
90
- return
91
- time.sleep(5)
92
- logger.error("Failed to initialize models")
93
-
94
- def extract_text_from_pdf(pdf_path: str) -> str:
95
- try:
96
- return " ".join(page.extract_text() for page in PdfReader(pdf_path).pages)
97
- except Exception as e:
98
- logger.error(f"PDF error: {str(e)}")
99
- raise HTTPException(400, "Invalid PDF file")
100
-
101
- def chunk_text(text: str) -> list:
102
- sentences = [s.strip() for s in text.split('.') if s.strip()]
103
- return ['. '.join(sentences[i:i+5]) + '.' for i in range(0, len(sentences), 5)]
104
-
105
- def analyze_content(text: str) -> Dict[str, float]:
106
- try:
107
- inputs = ai_tokenizer(
108
- text,
109
- truncation=True,
110
- padding='max_length',
111
- max_length=MAX_TEXT_LENGTH,
112
- return_tensors="pt"
113
- ).to(ai_model.device)
114
-
115
- with torch.no_grad():
116
- outputs = ai_model(**inputs)
117
- probs = torch.softmax(outputs.logits, dim=1).squeeze()
118
-
119
- return {
120
- "Human_Written": round(probs[0].item() * 100, 2),
121
- "AI_Generated": round(probs[1].item() * 100, 2)
122
- }
123
- except Exception as e:
124
- logger.error(f"AI analysis failed: {str(e)}")
125
- raise
126
-
127
- def calculate_plagiarism(chunks: list) -> float:
128
- if len(chunks) < 2:
129
- return 0.0
130
 
131
- embeddings = embedder.encode(chunks, batch_size=32)
132
- similarity_matrix = cosine_similarity(embeddings)
133
- np.fill_diagonal(similarity_matrix, 0)
134
 
135
- similar_pairs = np.sum(similarity_matrix > PLAGIARISM_THRESHOLD)
136
- total_possible = len(chunks) * (len(chunks) - 1) // 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
- return round((similar_pairs / total_possible) * 100, 2) if total_possible else 0.0
139
-
140
- @app.post("/analyze")
141
- async def analyze_essay(file: UploadFile = File(...)) -> Dict[str, Any]:
142
- if not model_status["model_loaded"]:
143
- raise HTTPException(503, "Service unavailable")
 
 
 
 
 
 
144
 
145
- if not file.filename.lower().endswith(".pdf"):
146
- raise HTTPException(400, "PDF files only")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
 
 
148
  try:
149
- with tempfile.TemporaryDirectory() as tmp_dir:
150
- # Save file
151
- file_path = f"{tmp_dir}/{uuid.uuid4()}.pdf"
152
- with open(file_path, "wb") as f:
153
- shutil.copyfileobj(file.file, f)
154
-
155
- # Process
156
- text = extract_text_from_pdf(file_path)
157
- if not text.strip():
158
- raise HTTPException(400, "Empty PDF content")
159
 
160
- return {
161
- "analysis": {
162
- **analyze_content(text),
163
- "Plagiarism_Score": calculate_plagiarism(chunk_text(text))
164
- },
165
- "status": "success"
166
- }
167
-
168
- except HTTPException:
169
- raise
170
  except Exception as e:
171
- logger.error(f"Processing failed: {str(e)}")
172
- raise HTTPException(500, "Analysis error")
173
 
174
  @app.get("/health")
175
- async def health_check() -> Dict[str, Any]:
176
- return {"status": "operational" if model_status["model_loaded"] else "degraded"}
177
-
178
- @app.get("/")
179
- async def root():
180
- return {"message": "Essay Analysis API - POST PDFs to /analyze"}
181
 
182
 
183
  # from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
 
1
  # app.py: AI Detection and Plagiarism Check API
2
+ import os
3
+ import time
4
+ import logging
5
+ import numpy as np
6
+ from fastapi import FastAPI, UploadFile, File, HTTPException
7
  from fastapi.responses import JSONResponse
8
+ from optimum.onnxruntime import ORTModelForSequenceClassification
9
+ from transformers import AutoTokenizer, pipeline
10
  from sentence_transformers import SentenceTransformer
 
11
  from PyPDF2 import PdfReader
 
 
 
 
 
 
12
  import tempfile
13
+ import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  # Configuration
 
 
 
16
  MODEL_NAME = "Essay-Grader/roberta-ai-detector-20250401_232702"
17
+ SENTENCE_MODEL = "sentence-transformers/all-MiniLM-L6-v2"
18
+ PLAGIARISM_THRESHOLD = 0.75
19
+ MAX_TEXT_LENGTH = 2048 # Reduced for CPU efficiency
20
+ BATCH_SIZE = 4
 
 
 
21
 
22
+ app = FastAPI(title="Essay Analyzer Pro", version="5.0")
 
 
 
23
 
24
+ # Initialize models
25
+ def load_models():
26
+ global ai_detector, embedder, tokenizer
27
 
28
+ # Load optimized ONNX model
29
+ ai_detector = ORTModelForSequenceClassification.from_pretrained(
30
+ MODEL_NAME,
31
+ provider="CPUExecutionProvider",
32
+ file_name="model_optimized.onnx"
33
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
 
 
36
 
37
+ # Initialize embedding model with CPU optimizations
38
+ embedder = SentenceTransformer(
39
+ SENTENCE_MODEL,
40
+ device="cpu",
41
+ modules_kwargs={"onnx_execution_provider": "CPUExecutionProvider"}
42
+ )
43
+
44
+ def process_pdf(file: UploadFile) -> str:
45
+ """Memory-efficient PDF processing"""
46
+ with tempfile.NamedTemporaryFile() as tmp:
47
+ tmp.write(file.file.read())
48
+ text = " ".join(
49
+ page.extract_text() or ""
50
+ for page in PdfReader(tmp.name).pages
51
+ )
52
+ return text.strip()
53
+
54
+ def analyze_text(text: str) -> dict:
55
+ """CPU-optimized analysis pipeline"""
56
+ start_time = time.time()
57
 
58
+ # Text preprocessing
59
+ text = text[:5000] # Strict length limit
60
+ chunks = [text[i:i+512] for i in range(0, len(text), 384)][:8]
61
+
62
+ # AI Detection
63
+ inputs = tokenizer(
64
+ chunks,
65
+ padding=True,
66
+ truncation=True,
67
+ max_length=512,
68
+ return_tensors="pt"
69
+ )
70
 
71
+ outputs = ai_detector(**inputs)
72
+ probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
73
+ human = probs[:, 0].mean().item() * 100
74
+ ai = probs[:, 1].mean().item() * 100
75
+
76
+ # Plagiarism Check
77
+ embeddings = embedder.encode(chunks, batch_size=BATCH_SIZE)
78
+ similarity = (embeddings @ embeddings.T) > PLAGIARISM_THRESHOLD
79
+ plagiarism = similarity.mean() * 100
80
+
81
+ return {
82
+ "human_written": round(human, 2),
83
+ "ai_generated": round(ai, 2),
84
+ "plagiarism_risk": round(plagiarism, 2),
85
+ "processing_time": round(time.time() - start_time, 2)
86
+ }
87
+
88
+ @app.on_event("startup")
89
+ async def startup_event():
90
+ load_models()
91
 
92
+ @app.post("/analyze")
93
+ async def analyze(file: UploadFile = File(...)):
94
  try:
95
+ if not file.filename.lower().endswith(".pdf"):
96
+ raise HTTPException(400, "Only PDF files accepted")
97
+
98
+ text = process_pdf(file)
99
+ if len(text) < 300:
100
+ raise HTTPException(400, "Text too short for analysis")
 
 
 
 
101
 
102
+ return JSONResponse(analyze_text(text))
103
+
 
 
 
 
 
 
 
 
104
  except Exception as e:
105
+ raise HTTPException(500, f"Analysis failed: {str(e)}")
 
106
 
107
  @app.get("/health")
108
+ async def health_check():
109
+ return {"status": "ready", "device": "cpu"}
 
 
 
 
110
 
111
 
112
  # from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
convert_model.py ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from optimum.onnxruntime import ORTModelForSequenceClassification
2
+
3
+ # Convert and optimize model
4
+ model = ORTModelForSequenceClassification.from_pretrained(
5
+ "Essay-Grader/roberta-ai-detector-20250401_232702",
6
+ export=True,
7
+ provider="CPUExecutionProvider"
8
+ )
9
+
10
+ # Save optimized model
11
+ model.save_pretrained(
12
+ "./optimized_model",
13
+ file_name="model_optimized.onnx"
14
+ )
15
+
16
+
model_quantizer.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import AutoModelForSequenceClassification
2
+ from optimum.onnxruntime import ORTOptimizer, ORTModelForSequenceClassification
3
+ from optimum.onnxruntime.configuration import OptimizationConfig
4
+
5
+ model = ORTModelForSequenceClassification.from_pretrained(
6
+ "Essay-Grader/roberta-ai-detector-20250401_232702",
7
+ from_transformers=True
8
+ )
9
+
10
+ optimizer = ORTOptimizer.from_pretrained(model)
11
+ optimization_config = OptimizationConfig(
12
+ optimization_level=99,
13
+ enable_transformers_specific_optimizations=True,
14
+ optimize_for_gpu=True,
15
+ fp16=True
16
+ )
17
+
18
+ optimizer.optimize(
19
+ save_dir="./optimized_model",
20
+ optimization_config=optimization_config
21
+ )
optimized_model/model_optimized.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:dba5166ad9db9ba648c1032ebbd34dcd0d085b50023b839ef5c68ca1db93a563
3
+ size 4
requirements.txt CHANGED
@@ -1,18 +1,29 @@
1
  # requirements.txt
2
 
3
- fastapi==0.115.0
4
- uvicorn==0.34.0
5
- transformers==4.41.0
6
- sentence-transformers==2.7.0
7
- torch==2.3.0
8
- scikit-learn==1.4.0
9
- PyPDF2==3.0.1
10
- numpy==1.26.4
11
- requests==2.31.0
12
- safetensors==0.4.3
13
- huggingface_hub>=0.23.0,<1.0
14
  python-multipart==0.0.9
15
- click==8.1.7
16
- accelerate>=0.30.0
17
- bitsandbytes>=0.43.0
18
- protobuf>=4.25.3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # requirements.txt
2
 
3
+ fastapi==0.109.2
4
+ uvicorn==0.27.1
5
+ sentence-transformers==2.6.1
6
+ transformers==4.38.2
7
+ torch==2.2.1+cpu --extra-index-url https://download.pytorch.org/whl/cpu
8
+ onnxruntime==1.17.1
9
+ optimum==1.17.1
10
+ pypdf2==3.0.1
11
+ nest-asyncio==1.6.0
 
 
12
  python-multipart==0.0.9
13
+
14
+ # fastapi==0.115.0
15
+ # uvicorn==0.34.0
16
+ # transformers==4.41.0
17
+ # sentence-transformers==2.7.0
18
+ # torch==2.3.0
19
+ # scikit-learn==1.4.0
20
+ # PyPDF2==3.0.1
21
+ # numpy==1.26.4
22
+ # requests==2.31.0
23
+ # safetensors==0.4.3
24
+ # huggingface_hub>=0.23.0,<1.0
25
+ # python-multipart==0.0.9
26
+ # click==8.1.7
27
+ # accelerate>=0.30.0
28
+ # bitsandbytes>=0.43.0
29
+ # protobuf>=4.25.3