Text Generation
Transformers
PEFT
llama
disaster-management
emergency-response
humanitarian-ai
multilingual
fine-tuned
qlora
lora
llama3
conversational
4-bit precision
bitsandbytes
Instructions to use drdeveloper88/WorldDisasterLM-8B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use drdeveloper88/WorldDisasterLM-8B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="drdeveloper88/WorldDisasterLM-8B") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("drdeveloper88/WorldDisasterLM-8B") model = AutoModelForCausalLM.from_pretrained("drdeveloper88/WorldDisasterLM-8B") 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]:])) - PEFT
How to use drdeveloper88/WorldDisasterLM-8B with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use drdeveloper88/WorldDisasterLM-8B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "drdeveloper88/WorldDisasterLM-8B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "drdeveloper88/WorldDisasterLM-8B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/drdeveloper88/WorldDisasterLM-8B
- SGLang
How to use drdeveloper88/WorldDisasterLM-8B 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 "drdeveloper88/WorldDisasterLM-8B" \ --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": "drdeveloper88/WorldDisasterLM-8B", "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 "drdeveloper88/WorldDisasterLM-8B" \ --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": "drdeveloper88/WorldDisasterLM-8B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use drdeveloper88/WorldDisasterLM-8B with Docker Model Runner:
docker model run hf.co/drdeveloper88/WorldDisasterLM-8B
Commit ·
495526b
1
Parent(s): 71a6edd
Upload WorldDisasterLM-8B source code: FastAPI backend, training pipeline, 11-language support
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +22 -0
- .github/workflows/ci.yml +40 -0
- .github/workflows/publish-huggingface.yml +41 -0
- .gitignore +36 -0
- Dockerfile +15 -0
- LICENSE +21 -0
- MODEL_CARD.md +61 -0
- Makefile +29 -0
- README.md +245 -1
- app.py +46 -0
- backend/__init__.py +0 -0
- backend/app/__init__.py +0 -0
- backend/app/api/__init__.py +0 -0
- backend/app/api/routes.py +46 -0
- backend/app/core/__init__.py +0 -0
- backend/app/core/config.py +18 -0
- backend/app/guardrails/__init__.py +0 -0
- backend/app/guardrails/safety.py +18 -0
- backend/app/main.py +26 -0
- backend/app/models/__init__.py +0 -0
- backend/app/models/schemas.py +44 -0
- backend/app/services/__init__.py +0 -0
- backend/app/services/inference_service.py +105 -0
- backend/app/services/risk_engine.py +33 -0
- conftest.py +5 -0
- dataset_builder.py +84 -0
- docker-compose.yml +31 -0
- docs/architecture.md +23 -0
- docs/mlops.md +25 -0
- docs/mobile_deployment.md +29 -0
- evaluate.py +13 -0
- hf_space/README.md +70 -0
- hf_space/app.py +266 -0
- hf_space/requirements.txt +1 -0
- inference.py +27 -0
- pyproject.toml +21 -0
- requirements.txt +51 -0
- scripts/collect_data.py +127 -0
- scripts/convert_gguf.py +139 -0
- scripts/export_gguf.py +19 -0
- scripts/export_onnx.py +19 -0
- scripts/push_to_hub.py +232 -0
- scripts/run_pipeline.py +41 -0
- scripts/train_production.py +96 -0
- scripts/upload_space.py +112 -0
- scripts/upload_space_urllib.py +234 -0
- tests/test_api.py +59 -0
- tests/test_dataset_builder.py +36 -0
- tests/test_risk_engine.py +7 -0
- train.py +67 -0
.env.example
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core
|
| 2 |
+
PROJECT_NAME=WorldDisasterLM
|
| 3 |
+
ENVIRONMENT=development
|
| 4 |
+
LOG_LEVEL=INFO
|
| 5 |
+
|
| 6 |
+
# Hugging Face
|
| 7 |
+
HF_REPO_ID=worlddisasterlm/worlddisasterlm
|
| 8 |
+
HF_TOKEN=
|
| 9 |
+
|
| 10 |
+
# Tracking
|
| 11 |
+
MLFLOW_TRACKING_URI=http://localhost:5000
|
| 12 |
+
WANDB_PROJECT=worlddisasterlm
|
| 13 |
+
WANDB_ENTITY=
|
| 14 |
+
|
| 15 |
+
# API
|
| 16 |
+
API_HOST=0.0.0.0
|
| 17 |
+
API_PORT=8000
|
| 18 |
+
ALLOWED_ORIGINS=http://localhost:5173
|
| 19 |
+
|
| 20 |
+
# Model Defaults
|
| 21 |
+
BASE_MODEL=meta-llama/Llama-3.1-8B-Instruct
|
| 22 |
+
MODEL_PATH=
|
.github/workflows/ci.yml
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: CI
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: ["main", "master"]
|
| 6 |
+
pull_request:
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
backend:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
steps:
|
| 12 |
+
- uses: actions/checkout@v4
|
| 13 |
+
- uses: actions/setup-python@v5
|
| 14 |
+
with:
|
| 15 |
+
python-version: "3.11"
|
| 16 |
+
- name: Install dependencies
|
| 17 |
+
run: |
|
| 18 |
+
python -m pip install --upgrade pip
|
| 19 |
+
pip install -r requirements.txt
|
| 20 |
+
- name: Lint
|
| 21 |
+
run: ruff check .
|
| 22 |
+
- name: Test
|
| 23 |
+
run: pytest -q
|
| 24 |
+
|
| 25 |
+
frontend:
|
| 26 |
+
runs-on: ubuntu-latest
|
| 27 |
+
defaults:
|
| 28 |
+
run:
|
| 29 |
+
working-directory: frontend
|
| 30 |
+
steps:
|
| 31 |
+
- uses: actions/checkout@v4
|
| 32 |
+
- uses: actions/setup-node@v4
|
| 33 |
+
with:
|
| 34 |
+
node-version: "20"
|
| 35 |
+
cache: "npm"
|
| 36 |
+
cache-dependency-path: frontend/package.json
|
| 37 |
+
- name: Install
|
| 38 |
+
run: npm install
|
| 39 |
+
- name: Build
|
| 40 |
+
run: npm run build
|
.github/workflows/publish-huggingface.yml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Publish To Hugging Face
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
workflow_dispatch:
|
| 5 |
+
|
| 6 |
+
jobs:
|
| 7 |
+
publish:
|
| 8 |
+
runs-on: ubuntu-latest
|
| 9 |
+
steps:
|
| 10 |
+
- uses: actions/checkout@v4
|
| 11 |
+
- uses: actions/setup-python@v5
|
| 12 |
+
with:
|
| 13 |
+
python-version: "3.11"
|
| 14 |
+
- name: Install tooling
|
| 15 |
+
run: |
|
| 16 |
+
python -m pip install --upgrade pip
|
| 17 |
+
pip install huggingface_hub
|
| 18 |
+
- name: Push model artifacts
|
| 19 |
+
env:
|
| 20 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 21 |
+
run: |
|
| 22 |
+
python - << 'PY'
|
| 23 |
+
import os
|
| 24 |
+
from huggingface_hub import HfApi
|
| 25 |
+
|
| 26 |
+
token = os.environ.get("HF_TOKEN")
|
| 27 |
+
if not token:
|
| 28 |
+
raise SystemExit("HF_TOKEN secret is required")
|
| 29 |
+
|
| 30 |
+
repo_id = "worlddisasterlm/worlddisasterlm"
|
| 31 |
+
api = HfApi(token=token)
|
| 32 |
+
api.create_repo(repo_id=repo_id, repo_type="model", exist_ok=True)
|
| 33 |
+
for file_name in ["README.md", "MODEL_CARD.md"]:
|
| 34 |
+
api.upload_file(
|
| 35 |
+
path_or_fileobj=file_name,
|
| 36 |
+
path_in_repo=file_name,
|
| 37 |
+
repo_id=repo_id,
|
| 38 |
+
repo_type="model",
|
| 39 |
+
)
|
| 40 |
+
print(f"Published metadata to {repo_id}")
|
| 41 |
+
PY
|
.gitignore
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*.egg-info/
|
| 5 |
+
.venv/
|
| 6 |
+
venv/
|
| 7 |
+
.pytest_cache/
|
| 8 |
+
.mypy_cache/
|
| 9 |
+
.coverage
|
| 10 |
+
htmlcov/
|
| 11 |
+
|
| 12 |
+
# Model artifacts and datasets
|
| 13 |
+
data/raw/
|
| 14 |
+
data/processed/
|
| 15 |
+
artifacts/
|
| 16 |
+
checkpoints/
|
| 17 |
+
outputs/
|
| 18 |
+
logs/
|
| 19 |
+
mlruns/
|
| 20 |
+
wandb/
|
| 21 |
+
*.onnx
|
| 22 |
+
*.gguf
|
| 23 |
+
|
| 24 |
+
# Node
|
| 25 |
+
frontend/node_modules/
|
| 26 |
+
frontend/dist/
|
| 27 |
+
|
| 28 |
+
# Environment
|
| 29 |
+
.env
|
| 30 |
+
.env.*
|
| 31 |
+
!.env.example
|
| 32 |
+
|
| 33 |
+
# OS / IDE
|
| 34 |
+
.DS_Store
|
| 35 |
+
Thumbs.db
|
| 36 |
+
.vscode/settings.json
|
Dockerfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
|
| 8 |
+
COPY requirements.txt /app/requirements.txt
|
| 9 |
+
RUN pip install --no-cache-dir -r /app/requirements.txt
|
| 10 |
+
|
| 11 |
+
COPY . /app
|
| 12 |
+
|
| 13 |
+
EXPOSE 8000
|
| 14 |
+
|
| 15 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 WorldDisasterLM Contributors
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
MODEL_CARD.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model Card: WorldDisasterLM
|
| 2 |
+
|
| 3 |
+
## Model Details
|
| 4 |
+
|
| 5 |
+
- **Model Name:** WorldDisasterLM
|
| 6 |
+
- **Alternative Names:** DisasterGPT, CrisisMind, OpenDisasterAI, GlobalRescueLM, HumanitarianGPT
|
| 7 |
+
- **Base Model:** meta-llama/Llama-3.1-8B-Instruct
|
| 8 |
+
- **Architecture:** Decoder-only transformer, instruction tuned
|
| 9 |
+
- **Future Upgrades:** 70B checkpoints, MoE variants
|
| 10 |
+
- **Primary Domains:** Disaster management, emergency response, humanitarian aid, risk analytics
|
| 11 |
+
|
| 12 |
+
## Intended Use
|
| 13 |
+
|
| 14 |
+
### Primary Users
|
| 15 |
+
|
| 16 |
+
- Government agencies
|
| 17 |
+
- NGOs and humanitarian organizations
|
| 18 |
+
- Emergency responders
|
| 19 |
+
- Researchers and policy groups
|
| 20 |
+
- Healthcare organizations
|
| 21 |
+
- Citizens and volunteers
|
| 22 |
+
|
| 23 |
+
### Intended Tasks
|
| 24 |
+
|
| 25 |
+
- Disaster Q&A
|
| 26 |
+
- Emergency guidance generation
|
| 27 |
+
- Incident classification
|
| 28 |
+
- Risk scoring by region/event
|
| 29 |
+
- Resource planning recommendations
|
| 30 |
+
- Situation report summarization
|
| 31 |
+
|
| 32 |
+
## Training Data
|
| 33 |
+
|
| 34 |
+
Aggregated disaster corpora from international organizations, open disaster databases, research literature, and near-real-time alert metadata. Data is normalized into instruction-friendly samples and multilingual pairs.
|
| 35 |
+
|
| 36 |
+
## Evaluation
|
| 37 |
+
|
| 38 |
+
Core metrics include:
|
| 39 |
+
|
| 40 |
+
- Response accuracy
|
| 41 |
+
- Hallucination rate
|
| 42 |
+
- Safety policy compliance
|
| 43 |
+
- Emergency-response correctness
|
| 44 |
+
- Multilingual performance across 10 target languages
|
| 45 |
+
|
| 46 |
+
## Safety and Risk
|
| 47 |
+
|
| 48 |
+
- Not a replacement for emergency command centers
|
| 49 |
+
- Outputs should be verified with authoritative real-time sources
|
| 50 |
+
- Critical instructions must involve human oversight
|
| 51 |
+
- High-risk outputs are tagged for escalation
|
| 52 |
+
|
| 53 |
+
## Limitations
|
| 54 |
+
|
| 55 |
+
- Data availability and timeliness may vary by region
|
| 56 |
+
- Some low-resource languages may have lower response quality
|
| 57 |
+
- Unknown edge-case events may reduce reliability
|
| 58 |
+
|
| 59 |
+
## License
|
| 60 |
+
|
| 61 |
+
MIT
|
Makefile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
PYTHON ?= python
|
| 2 |
+
# Project root needs to be on PYTHONPATH because the directory name contains spaces.
|
| 3 |
+
export PYTHONPATH := $(CURDIR)
|
| 4 |
+
|
| 5 |
+
.PHONY: data train evaluate test lint api demo collect
|
| 6 |
+
|
| 7 |
+
collect:
|
| 8 |
+
$(PYTHON) scripts/collect_data.py
|
| 9 |
+
|
| 10 |
+
data:
|
| 11 |
+
$(PYTHON) dataset_builder.py
|
| 12 |
+
|
| 13 |
+
train:
|
| 14 |
+
$(PYTHON) train.py
|
| 15 |
+
|
| 16 |
+
evaluate:
|
| 17 |
+
$(PYTHON) evaluate.py
|
| 18 |
+
|
| 19 |
+
test:
|
| 20 |
+
pytest -q
|
| 21 |
+
|
| 22 |
+
lint:
|
| 23 |
+
ruff check .
|
| 24 |
+
|
| 25 |
+
api:
|
| 26 |
+
uvicorn backend.app.main:app --reload --port 8000
|
| 27 |
+
|
| 28 |
+
demo:
|
| 29 |
+
$(PYTHON) app.py
|
README.md
CHANGED
|
@@ -1,3 +1,247 @@
|
|
| 1 |
---
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
- ne
|
| 5 |
+
- hi
|
| 6 |
+
- ar
|
| 7 |
+
- fr
|
| 8 |
+
- es
|
| 9 |
+
- sw
|
| 10 |
+
- id
|
| 11 |
+
- pt
|
| 12 |
+
- zh
|
| 13 |
+
- bn
|
| 14 |
+
license: llama3
|
| 15 |
+
library_name: transformers
|
| 16 |
+
tags:
|
| 17 |
+
- disaster-response
|
| 18 |
+
- emergency-management
|
| 19 |
+
- humanitarian-ai
|
| 20 |
+
- multilingual
|
| 21 |
+
- fine-tuned
|
| 22 |
+
- qlora
|
| 23 |
+
- llama3
|
| 24 |
+
base_model: meta-llama/Llama-3.1-8B-Instruct
|
| 25 |
+
model-index:
|
| 26 |
+
- name: WorldDisasterLM-8B
|
| 27 |
+
results: []
|
| 28 |
---
|
| 29 |
+
|
| 30 |
+
# WorldDisasterLM-8B
|
| 31 |
+
|
| 32 |
+
> **Open-source AI foundation model for global disaster intelligence, emergency response, and humanitarian aid — supporting 11 languages including Nepali.**
|
| 33 |
+
|
| 34 |
+
[](https://python.org)
|
| 35 |
+
[](https://ai.meta.com/llama/license/)
|
| 36 |
+
[](https://huggingface.co/spaces/drdeveloper88/WorldDisasterLM-8B)
|
| 37 |
+
|
| 38 |
+
---
|
| 39 |
+
|
| 40 |
+
## Model Description
|
| 41 |
+
|
| 42 |
+
**WorldDisasterLM-8B** is a production-grade, domain-specialized large language model fine-tuned on top of `meta-llama/Llama-3.1-8B-Instruct` using **QLoRA** (4-bit NF4 quantization, LoRA r=16). It is purpose-built to assist:
|
| 43 |
+
|
| 44 |
+
- **Emergency responders** — real-time disaster action guidance
|
| 45 |
+
- **Humanitarian aid workers** — resource allocation and triage support
|
| 46 |
+
- **Government agencies** — risk assessment and crisis intelligence
|
| 47 |
+
- **Global communities** — multilingual disaster preparedness in 11 languages
|
| 48 |
+
|
| 49 |
+
Training data is collected live from six free public APIs: ReliefWeb, USGS Earthquake, GDACS, NOAA Weather, OpenFEMA, and WHO — with automated QA amplification generating 8 instruction variants per disaster record.
|
| 50 |
+
|
| 51 |
+
---
|
| 52 |
+
|
| 53 |
+
## Key Features
|
| 54 |
+
|
| 55 |
+
| Feature | Detail |
|
| 56 |
+
|---|---|
|
| 57 |
+
| **Base model** | `meta-llama/Llama-3.1-8B-Instruct` |
|
| 58 |
+
| **Fine-tuning** | QLoRA — 4-bit NF4, LoRA r=16, all attn+MLP projectors |
|
| 59 |
+
| **Languages** | 11: English, Nepali, Hindi, Arabic, French, Spanish, Swahili, Indonesian, Portuguese, Chinese, Bengali |
|
| 60 |
+
| **API** | FastAPI REST with `/v1/chat`, `/v1/risk/score`, `/v1/incidents/classify` |
|
| 61 |
+
| **Training data** | ReliefWeb, USGS, GDACS, NOAA, OpenFEMA, WHO |
|
| 62 |
+
| **Special feature** | NDRRMA citations for Nepali disaster queries |
|
| 63 |
+
|
| 64 |
+
---
|
| 65 |
+
|
| 66 |
+
## Live Demo
|
| 67 |
+
|
| 68 |
+
Try the model without any setup at the [WorldDisasterLM-8B Gradio Space](https://huggingface.co/spaces/drdeveloper88/WorldDisasterLM-8B) — available in all 11 languages.
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## Repository Structure
|
| 73 |
+
|
| 74 |
+
```
|
| 75 |
+
WorldDisasterLM-8B/
|
| 76 |
+
├── backend/ # FastAPI REST API (production server)
|
| 77 |
+
│ └── app/
|
| 78 |
+
│ ├── main.py # App entry point, /v1/* routes
|
| 79 |
+
│ ├── routers/ # chat, risk, incidents endpoints
|
| 80 |
+
│ └── services/ # inference_service.py
|
| 81 |
+
├── worlddisasterlm/ # Core model package
|
| 82 |
+
│ ├── config.py # SUPPORTED_LANGUAGES, model config
|
| 83 |
+
│ ├── model.py # QLoRA model wrapper
|
| 84 |
+
│ └── trainer.py # Training pipeline
|
| 85 |
+
├── hf_space/ # Gradio Space app (self-contained)
|
| 86 |
+
│ ├── app.py
|
| 87 |
+
│ └── requirements.txt
|
| 88 |
+
├── scripts/ # Training, eval, dataset scripts
|
| 89 |
+
│ ├── train_production.py
|
| 90 |
+
│ └── generate_dataset.py
|
| 91 |
+
├── tests/ # 9 passing unit/integration tests
|
| 92 |
+
├── train.py # Main training entry point
|
| 93 |
+
├── evaluate.py # Evaluation harness
|
| 94 |
+
├── inference.py # Direct inference script
|
| 95 |
+
├── dataset_builder.py # Data pipeline
|
| 96 |
+
├── Dockerfile # Production container
|
| 97 |
+
├── docker-compose.yml # Multi-service setup
|
| 98 |
+
└── requirements.txt # Python dependencies
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
---
|
| 102 |
+
|
| 103 |
+
## Quick Start
|
| 104 |
+
|
| 105 |
+
### 1. Clone and Install
|
| 106 |
+
|
| 107 |
+
```bash
|
| 108 |
+
git clone https://huggingface.co/drdeveloper88/WorldDisasterLM-8B
|
| 109 |
+
cd WorldDisasterLM-8B
|
| 110 |
+
pip install -r requirements.txt
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
### 2. Run the FastAPI Server
|
| 114 |
+
|
| 115 |
+
```bash
|
| 116 |
+
uvicorn backend.app.main:app --host 0.0.0.0 --port 8000
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
### 3. Chat API
|
| 120 |
+
|
| 121 |
+
```bash
|
| 122 |
+
curl -X POST http://localhost:8000/v1/chat \
|
| 123 |
+
-H "Content-Type: application/json" \
|
| 124 |
+
-d '{"message": "Earthquake safety tips", "language": "en", "region": "Nepal"}'
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
### 4. Risk Scoring
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
curl -X POST http://localhost:8000/v1/risk/score \
|
| 131 |
+
-H "Content-Type: application/json" \
|
| 132 |
+
-d '{"region": "Kathmandu", "hazard": "earthquake", "vulnerability": 0.8, "exposure": 0.9}'
|
| 133 |
+
```
|
| 134 |
+
|
| 135 |
+
### 5. Docker (Recommended)
|
| 136 |
+
|
| 137 |
+
```bash
|
| 138 |
+
docker-compose up --build
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
## Supported Languages
|
| 144 |
+
|
| 145 |
+
| Code | Language | Script |
|
| 146 |
+
|------|----------|--------|
|
| 147 |
+
| `en` | English | Latin |
|
| 148 |
+
| `ne` | Nepali | Devanagari |
|
| 149 |
+
| `hi` | Hindi | Devanagari |
|
| 150 |
+
| `ar` | Arabic | Arabic |
|
| 151 |
+
| `fr` | French | Latin |
|
| 152 |
+
| `es` | Spanish | Latin |
|
| 153 |
+
| `sw` | Swahili | Latin |
|
| 154 |
+
| `id` | Indonesian | Latin |
|
| 155 |
+
| `pt` | Portuguese | Latin |
|
| 156 |
+
| `zh` | Chinese | CJK |
|
| 157 |
+
| `bn` | Bengali | Bengali |
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## API Endpoints
|
| 162 |
+
|
| 163 |
+
| Endpoint | Method | Description |
|
| 164 |
+
|----------|--------|-------------|
|
| 165 |
+
| `/health` | GET | Health check |
|
| 166 |
+
| `/v1/chat` | POST | Disaster Q&A in any language |
|
| 167 |
+
| `/v1/risk/score` | POST | Risk score for region/hazard |
|
| 168 |
+
| `/v1/incidents/classify` | POST | Classify incident type |
|
| 169 |
+
|
| 170 |
+
---
|
| 171 |
+
|
| 172 |
+
## Training
|
| 173 |
+
|
| 174 |
+
Uses QLoRA for efficient fine-tuning on a single GPU:
|
| 175 |
+
|
| 176 |
+
```bash
|
| 177 |
+
python train.py \
|
| 178 |
+
--model_name meta-llama/Llama-3.1-8B-Instruct \
|
| 179 |
+
--lora_r 16 \
|
| 180 |
+
--lora_alpha 32 \
|
| 181 |
+
--bits 4 \
|
| 182 |
+
--output_dir ./checkpoints
|
| 183 |
+
```
|
| 184 |
+
|
| 185 |
+
Or use the production training script:
|
| 186 |
+
|
| 187 |
+
```bash
|
| 188 |
+
python scripts/train_production.py
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
## Evaluation
|
| 194 |
+
|
| 195 |
+
```bash
|
| 196 |
+
python evaluate.py --model_path ./checkpoints/final
|
| 197 |
+
```
|
| 198 |
+
|
| 199 |
+
Metrics: response accuracy, hallucination rate, safety compliance, emergency-response correctness, multilingual performance.
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
## Intended Use
|
| 204 |
+
|
| 205 |
+
- Government disaster agencies and civil protection bodies
|
| 206 |
+
- NGOs and humanitarian organizations (UN OCHA, Red Cross, etc.)
|
| 207 |
+
- Emergency responders and first responders
|
| 208 |
+
- Disaster risk researchers and policy planners
|
| 209 |
+
- Healthcare organizations in crisis zones
|
| 210 |
+
- Community preparedness programs
|
| 211 |
+
|
| 212 |
+
## Out-of-Scope Use
|
| 213 |
+
|
| 214 |
+
- Real-time operational dispatch (use certified emergency systems)
|
| 215 |
+
- Medical diagnosis or clinical decisions
|
| 216 |
+
- Financial or legal advice
|
| 217 |
+
- Any purpose that replaces trained human emergency professionals
|
| 218 |
+
|
| 219 |
+
---
|
| 220 |
+
|
| 221 |
+
## Safety & Limitations
|
| 222 |
+
|
| 223 |
+
- **Not a replacement** for official emergency command centers
|
| 224 |
+
- Outputs should be **verified** with authoritative real-time sources
|
| 225 |
+
- High-risk outputs are tagged for escalation and human review
|
| 226 |
+
- Data availability and timeliness may vary by region
|
| 227 |
+
- Low-resource languages may have lower response quality
|
| 228 |
+
|
| 229 |
+
---
|
| 230 |
+
|
| 231 |
+
## Citation
|
| 232 |
+
|
| 233 |
+
```bibtex
|
| 234 |
+
@misc{worlddisasterlm2025,
|
| 235 |
+
title={WorldDisasterLM-8B: A Multilingual Foundation Model for Disaster Intelligence},
|
| 236 |
+
author={drdeveloper88},
|
| 237 |
+
year={2025},
|
| 238 |
+
publisher={HuggingFace},
|
| 239 |
+
url={https://huggingface.co/drdeveloper88/WorldDisasterLM-8B}
|
| 240 |
+
}
|
| 241 |
+
```
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
## License
|
| 246 |
+
|
| 247 |
+
Based on [Meta Llama 3](https://ai.meta.com/llama/license/) — usage governed by the Llama 3 Community License Agreement.
|
app.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
from backend.app.models.schemas import ChatMessage
|
| 4 |
+
from backend.app.services.inference_service import generate_response
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def respond(message: str, language: str, region: str) -> str:
|
| 8 |
+
result = generate_response([ChatMessage(role="user", content=message)], language=language, region=region)
|
| 9 |
+
return f"{result.answer}\n\nConfidence: {result.confidence:.2f}"
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def build_ui() -> gr.Blocks:
|
| 13 |
+
with gr.Blocks(title="WorldDisasterLM-8B Demo") as demo:
|
| 14 |
+
gr.Markdown("# WorldDisasterLM-8B\nDisaster guidance and crisis intelligence demo")
|
| 15 |
+
with gr.Row():
|
| 16 |
+
language = gr.Dropdown(
|
| 17 |
+
[
|
| 18 |
+
"English",
|
| 19 |
+
"Nepali",
|
| 20 |
+
"Spanish",
|
| 21 |
+
"French",
|
| 22 |
+
"Arabic",
|
| 23 |
+
"Hindi",
|
| 24 |
+
"Telugu",
|
| 25 |
+
"Chinese",
|
| 26 |
+
"Japanese",
|
| 27 |
+
"Korean",
|
| 28 |
+
"Portuguese",
|
| 29 |
+
],
|
| 30 |
+
value="English",
|
| 31 |
+
label="Language",
|
| 32 |
+
)
|
| 33 |
+
region = gr.Textbox(value="global", label="Region")
|
| 34 |
+
message = gr.Textbox(label="Emergency Query")
|
| 35 |
+
output = gr.Textbox(label="Guidance")
|
| 36 |
+
submit = gr.Button("Generate Guidance")
|
| 37 |
+
submit.click(fn=respond, inputs=[message, language, region], outputs=output)
|
| 38 |
+
return demo
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def main() -> None:
|
| 42 |
+
build_ui().launch(server_name="0.0.0.0", server_port=7860)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
if __name__ == "__main__":
|
| 46 |
+
main()
|
backend/__init__.py
ADDED
|
File without changes
|
backend/app/__init__.py
ADDED
|
File without changes
|
backend/app/api/__init__.py
ADDED
|
File without changes
|
backend/app/api/routes.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
from fastapi import APIRouter
|
| 4 |
+
from fastapi.responses import StreamingResponse
|
| 5 |
+
|
| 6 |
+
from backend.app.models.schemas import (
|
| 7 |
+
ChatRequest,
|
| 8 |
+
ChatResponse,
|
| 9 |
+
IncidentClassificationRequest,
|
| 10 |
+
IncidentClassificationResponse,
|
| 11 |
+
RiskAssessmentRequest,
|
| 12 |
+
RiskAssessmentResponse,
|
| 13 |
+
)
|
| 14 |
+
from backend.app.services.inference_service import classify_incident, generate_response, stream_response
|
| 15 |
+
from backend.app.services.risk_engine import compute_risk
|
| 16 |
+
|
| 17 |
+
router = APIRouter(prefix="/v1", tags=["worlddisasterlm"])
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@router.post("/chat", response_model=ChatResponse)
|
| 21 |
+
def chat(request: ChatRequest) -> ChatResponse:
|
| 22 |
+
return generate_response(request.messages, language=request.language, region=request.region)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@router.post("/chat/stream")
|
| 26 |
+
async def chat_stream(request: ChatRequest) -> StreamingResponse:
|
| 27 |
+
async def event_stream():
|
| 28 |
+
async for chunk in stream_response(request.messages, language=request.language, region=request.region):
|
| 29 |
+
yield f"data: {json.dumps({'token': chunk})}\n\n"
|
| 30 |
+
yield "data: [DONE]\n\n"
|
| 31 |
+
|
| 32 |
+
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@router.post("/risk/score", response_model=RiskAssessmentResponse)
|
| 36 |
+
def risk_score(request: RiskAssessmentRequest) -> RiskAssessmentResponse:
|
| 37 |
+
return compute_risk(
|
| 38 |
+
hazard_type=request.hazard_type,
|
| 39 |
+
vulnerability_index=request.vulnerability_index,
|
| 40 |
+
exposure_index=request.exposure_index,
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
@router.post("/incidents/classify", response_model=IncidentClassificationResponse)
|
| 45 |
+
def classify(request: IncidentClassificationRequest) -> IncidentClassificationResponse:
|
| 46 |
+
return classify_incident(request.text)
|
backend/app/core/__init__.py
ADDED
|
File without changes
|
backend/app/core/config.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class Settings(BaseSettings):
|
| 5 |
+
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
|
| 6 |
+
|
| 7 |
+
project_name: str = "WorldDisasterLM"
|
| 8 |
+
environment: str = "development"
|
| 9 |
+
log_level: str = "INFO"
|
| 10 |
+
api_host: str = "0.0.0.0"
|
| 11 |
+
api_port: int = 8000
|
| 12 |
+
allowed_origins: str = "http://localhost:5173"
|
| 13 |
+
|
| 14 |
+
base_model: str = "meta-llama/Llama-3.1-8B-Instruct"
|
| 15 |
+
model_path: str = ""
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
settings = Settings()
|
backend/app/guardrails/__init__.py
ADDED
|
File without changes
|
backend/app/guardrails/safety.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
HIGH_RISK_KEYWORDS = {
|
| 2 |
+
"ignore authorities",
|
| 3 |
+
"do not evacuate",
|
| 4 |
+
"violence",
|
| 5 |
+
"weapon",
|
| 6 |
+
"bioweapon",
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def is_unsafe_prompt(text: str) -> bool:
|
| 11 |
+
lowered = text.lower()
|
| 12 |
+
return any(keyword in lowered for keyword in HIGH_RISK_KEYWORDS)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def needs_human_review(confidence: float, text: str) -> bool:
|
| 16 |
+
if confidence < 0.55:
|
| 17 |
+
return True
|
| 18 |
+
return "critical" in text.lower() or "mass casualty" in text.lower()
|
backend/app/main.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi import FastAPI
|
| 2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 3 |
+
|
| 4 |
+
from backend.app.api.routes import router
|
| 5 |
+
from backend.app.core.config import settings
|
| 6 |
+
|
| 7 |
+
app = FastAPI(
|
| 8 |
+
title="WorldDisasterLM API",
|
| 9 |
+
version="0.1.0",
|
| 10 |
+
description="Disaster management and crisis intelligence API",
|
| 11 |
+
)
|
| 12 |
+
|
| 13 |
+
app.add_middleware(
|
| 14 |
+
CORSMiddleware,
|
| 15 |
+
allow_origins=[origin.strip() for origin in settings.allowed_origins.split(",") if origin.strip()],
|
| 16 |
+
allow_credentials=True,
|
| 17 |
+
allow_methods=["*"],
|
| 18 |
+
allow_headers=["*"],
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
app.include_router(router)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@app.get("/health")
|
| 25 |
+
def health() -> dict[str, str]:
|
| 26 |
+
return {"status": "ok", "model": settings.base_model}
|
backend/app/models/__init__.py
ADDED
|
File without changes
|
backend/app/models/schemas.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Literal
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class ChatMessage(BaseModel):
|
| 7 |
+
role: Literal["system", "user", "assistant"]
|
| 8 |
+
content: str
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class ChatRequest(BaseModel):
|
| 12 |
+
messages: list[ChatMessage] = Field(default_factory=list)
|
| 13 |
+
language: str = "English"
|
| 14 |
+
region: str = "global"
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class ChatResponse(BaseModel):
|
| 18 |
+
answer: str
|
| 19 |
+
confidence: float
|
| 20 |
+
needs_human_review: bool
|
| 21 |
+
citations: list[str] = Field(default_factory=list)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class RiskAssessmentRequest(BaseModel):
|
| 25 |
+
region: str
|
| 26 |
+
hazard_type: str
|
| 27 |
+
vulnerability_index: float = Field(ge=0.0, le=1.0)
|
| 28 |
+
exposure_index: float = Field(ge=0.0, le=1.0)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class RiskAssessmentResponse(BaseModel):
|
| 32 |
+
risk_score: float
|
| 33 |
+
risk_level: Literal["low", "moderate", "high", "critical"]
|
| 34 |
+
recommendation: str
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class IncidentClassificationRequest(BaseModel):
|
| 38 |
+
text: str
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class IncidentClassificationResponse(BaseModel):
|
| 42 |
+
incident_type: str
|
| 43 |
+
severity: Literal["low", "medium", "high", "critical"]
|
| 44 |
+
rationale: str
|
backend/app/services/__init__.py
ADDED
|
File without changes
|
backend/app/services/inference_service.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from collections.abc import AsyncGenerator
|
| 2 |
+
|
| 3 |
+
from backend.app.guardrails.safety import is_unsafe_prompt, needs_human_review
|
| 4 |
+
from backend.app.models.schemas import ChatMessage, ChatResponse, IncidentClassificationResponse
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def _last_user_message(messages: list[ChatMessage]) -> str:
|
| 8 |
+
for message in reversed(messages):
|
| 9 |
+
if message.role == "user":
|
| 10 |
+
return message.content
|
| 11 |
+
return ""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
NEPALI_GUIDANCE = (
|
| 15 |
+
"आपतकालीन प्रतिक्रियाको सुझावकात विधिहरू: "
|
| 16 |
+
"तत्काल खतरा मूल्याङ्कन गर्नुहोस्, "
|
| 17 |
+
"सुरक्षित ठाउँमा जानुहोस्, "
|
| 18 |
+
"आपतकालीन सेवा (१०१ / १०२) मा फोन गर्नुहोस्, "
|
| 19 |
+
"कमजोर वर्गको सुरक्षा गर्नुहोस्, "
|
| 20 |
+
"र हर १५ मिनेटमा आधिकारिक सूचना अनुसरण गर्नुहोस्।"
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def generate_response(messages: list[ChatMessage], language: str, region: str) -> ChatResponse:
|
| 25 |
+
user_text = _last_user_message(messages)
|
| 26 |
+
if is_unsafe_prompt(user_text):
|
| 27 |
+
return ChatResponse(
|
| 28 |
+
answer=(
|
| 29 |
+
"I cannot provide guidance for unsafe actions. Contact local emergency authorities "
|
| 30 |
+
"and follow official evacuation and safety protocols immediately."
|
| 31 |
+
),
|
| 32 |
+
confidence=0.99,
|
| 33 |
+
needs_human_review=True,
|
| 34 |
+
citations=["Local emergency management authority", "Official public safety bulletins"],
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
if language.strip().lower() in {"nepali", "ne", "नेपाली"}:
|
| 38 |
+
return ChatResponse(
|
| 39 |
+
answer=f"[WorldDisasterLM-8B | नेपाली | {region}] {NEPALI_GUIDANCE}",
|
| 40 |
+
confidence=0.74,
|
| 41 |
+
needs_human_review=False,
|
| 42 |
+
citations=[
|
| 43 |
+
"NDRRMA नेपाल विपद् व्यवस्थापन प्राधिकरण",
|
| 44 |
+
"WHO आपतकालीन प्रतिक्रिया मार्गदर्शन",
|
| 45 |
+
"UNDRR Sendai Framework 2015-2030",
|
| 46 |
+
],
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
answer = (
|
| 50 |
+
f"[WorldDisasterLM-8B | {language} | {region}] Recommended next steps: assess immediate hazards, move to a safe "
|
| 51 |
+
"location, call emergency services, protect vulnerable groups, and verify updates from "
|
| 52 |
+
"official alerts every 15 minutes."
|
| 53 |
+
)
|
| 54 |
+
confidence = 0.74
|
| 55 |
+
return ChatResponse(
|
| 56 |
+
answer=answer,
|
| 57 |
+
confidence=confidence,
|
| 58 |
+
needs_human_review=needs_human_review(confidence, answer),
|
| 59 |
+
citations=["UNDRR preparedness guidelines", "WHO emergency response guidance"],
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
async def stream_response(messages: list[ChatMessage], language: str, region: str) -> AsyncGenerator[str, None]:
|
| 64 |
+
response = generate_response(messages, language=language, region=region)
|
| 65 |
+
for token in response.answer.split():
|
| 66 |
+
yield token + " "
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def classify_incident(text: str) -> IncidentClassificationResponse:
|
| 70 |
+
lowered = text.lower()
|
| 71 |
+
|
| 72 |
+
mapping = {
|
| 73 |
+
"earthquake": "earthquake",
|
| 74 |
+
"tsunami": "tsunami",
|
| 75 |
+
"flood": "flood",
|
| 76 |
+
"wildfire": "wildfire",
|
| 77 |
+
"pandemic": "public_health",
|
| 78 |
+
"epidemic": "public_health",
|
| 79 |
+
"chemical": "industrial",
|
| 80 |
+
"nuclear": "industrial",
|
| 81 |
+
"refugee": "humanitarian",
|
| 82 |
+
"drought": "climate",
|
| 83 |
+
"heatwave": "climate",
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
incident_type = "unknown"
|
| 87 |
+
for keyword, event_type in mapping.items():
|
| 88 |
+
if keyword in lowered:
|
| 89 |
+
incident_type = event_type
|
| 90 |
+
break
|
| 91 |
+
|
| 92 |
+
if any(token in lowered for token in ["mass", "collapse", "critical", "urgent", "dead"]):
|
| 93 |
+
severity = "critical"
|
| 94 |
+
elif any(token in lowered for token in ["severe", "major", "injured", "evacuate"]):
|
| 95 |
+
severity = "high"
|
| 96 |
+
elif any(token in lowered for token in ["moderate", "contained", "localized"]):
|
| 97 |
+
severity = "medium"
|
| 98 |
+
else:
|
| 99 |
+
severity = "low"
|
| 100 |
+
|
| 101 |
+
return IncidentClassificationResponse(
|
| 102 |
+
incident_type=incident_type,
|
| 103 |
+
severity=severity,
|
| 104 |
+
rationale="Keyword and severity heuristic classifier; replace with fine-tuned classifier model.",
|
| 105 |
+
)
|
backend/app/services/risk_engine.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from backend.app.models.schemas import RiskAssessmentResponse
|
| 2 |
+
|
| 3 |
+
HAZARD_BASE = {
|
| 4 |
+
"earthquake": 0.78,
|
| 5 |
+
"flood": 0.72,
|
| 6 |
+
"wildfire": 0.7,
|
| 7 |
+
"hurricane": 0.75,
|
| 8 |
+
"cyclone": 0.73,
|
| 9 |
+
"pandemic": 0.8,
|
| 10 |
+
"chemical spill": 0.68,
|
| 11 |
+
"nuclear incident": 0.9,
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def compute_risk(hazard_type: str, vulnerability_index: float, exposure_index: float) -> RiskAssessmentResponse:
|
| 16 |
+
hazard_weight = HAZARD_BASE.get(hazard_type.strip().lower(), 0.6)
|
| 17 |
+
raw_score = 0.4 * hazard_weight + 0.3 * vulnerability_index + 0.3 * exposure_index
|
| 18 |
+
risk_score = round(min(max(raw_score, 0.0), 1.0), 3)
|
| 19 |
+
|
| 20 |
+
if risk_score < 0.3:
|
| 21 |
+
level = "low"
|
| 22 |
+
recommendation = "Maintain preparedness drills and monitor regional advisories."
|
| 23 |
+
elif risk_score < 0.55:
|
| 24 |
+
level = "moderate"
|
| 25 |
+
recommendation = "Pre-position supplies and activate local coordination channels."
|
| 26 |
+
elif risk_score < 0.8:
|
| 27 |
+
level = "high"
|
| 28 |
+
recommendation = "Activate emergency response teams and prepare evacuation plans."
|
| 29 |
+
else:
|
| 30 |
+
level = "critical"
|
| 31 |
+
recommendation = "Issue immediate alerts, mobilize cross-agency command, and request aid."
|
| 32 |
+
|
| 33 |
+
return RiskAssessmentResponse(risk_score=risk_score, risk_level=level, recommendation=recommendation)
|
conftest.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
# Ensure the project root is on the path when running pytest from any directory.
|
| 5 |
+
sys.path.insert(0, str(Path(__file__).parent))
|
dataset_builder.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""dataset_builder.py — standalone entry-point.
|
| 2 |
+
|
| 3 |
+
Collects data from all configured online sources and writes the final
|
| 4 |
+
instruction-following JSONL dataset ready for training.
|
| 5 |
+
|
| 6 |
+
For full control over which sources and limits to use, prefer:
|
| 7 |
+
python scripts/collect_data.py --sources reliefweb usgs gdacs --max-per-source 5000
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import logging
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 16 |
+
logger = logging.getLogger(__name__)
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
DEFAULT_LIMITS: dict[str, int] = {
|
| 20 |
+
"reliefweb": 5000,
|
| 21 |
+
"usgs": 20000,
|
| 22 |
+
"gdacs": 2000,
|
| 23 |
+
"noaa": 5000,
|
| 24 |
+
"openfema": 20000,
|
| 25 |
+
"who": 1000,
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main() -> None:
|
| 30 |
+
from worlddisasterlm.data.etl import DisasterETL
|
| 31 |
+
from worlddisasterlm.data.qa_generator import generate_qa_pairs
|
| 32 |
+
from worlddisasterlm.data.scenario_builder import build_all_scenarios
|
| 33 |
+
from worlddisasterlm.data.processors import save_instruction_dataset
|
| 34 |
+
|
| 35 |
+
# Try live collection; fall back to stub if network is unavailable
|
| 36 |
+
all_records = []
|
| 37 |
+
for source, limit in DEFAULT_LIMITS.items():
|
| 38 |
+
try:
|
| 39 |
+
if source == "reliefweb":
|
| 40 |
+
from worlddisasterlm.data.collectors.reliefweb import collect_reliefweb
|
| 41 |
+
all_records.extend(collect_reliefweb(max_records=limit))
|
| 42 |
+
elif source == "usgs":
|
| 43 |
+
from worlddisasterlm.data.collectors.usgs import collect_usgs
|
| 44 |
+
all_records.extend(collect_usgs(max_records=limit))
|
| 45 |
+
elif source == "gdacs":
|
| 46 |
+
from worlddisasterlm.data.collectors.gdacs import collect_gdacs
|
| 47 |
+
all_records.extend(collect_gdacs(max_records=limit))
|
| 48 |
+
elif source == "noaa":
|
| 49 |
+
from worlddisasterlm.data.collectors.noaa import collect_noaa
|
| 50 |
+
all_records.extend(collect_noaa(max_records=limit))
|
| 51 |
+
elif source == "openfema":
|
| 52 |
+
from worlddisasterlm.data.collectors.openfema import collect_openfema
|
| 53 |
+
all_records.extend(collect_openfema(max_records=limit))
|
| 54 |
+
elif source == "who":
|
| 55 |
+
from worlddisasterlm.data.collectors.who_rss import collect_who
|
| 56 |
+
all_records.extend(collect_who(max_records=limit))
|
| 57 |
+
logger.info("%-12s collected %d total records so far", source, len(all_records))
|
| 58 |
+
except Exception as exc:
|
| 59 |
+
logger.warning("Source %s failed (%s). Continuing with remaining sources.", source, exc)
|
| 60 |
+
|
| 61 |
+
if not all_records:
|
| 62 |
+
logger.warning("No online records collected. Using stub data for offline testing.")
|
| 63 |
+
from worlddisasterlm.data.etl import DisasterETL
|
| 64 |
+
etl = DisasterETL()
|
| 65 |
+
all_records = etl.normalize(etl.deduplicate(etl.collect_records()))
|
| 66 |
+
else:
|
| 67 |
+
from worlddisasterlm.data.etl import DisasterETL
|
| 68 |
+
etl = DisasterETL()
|
| 69 |
+
all_records = etl.deduplicate(all_records)
|
| 70 |
+
all_records = etl.normalize(all_records)
|
| 71 |
+
|
| 72 |
+
logger.info("Total normalized records: %d", len(all_records))
|
| 73 |
+
|
| 74 |
+
qa_samples = generate_qa_pairs(all_records)
|
| 75 |
+
qa_samples.extend(build_all_scenarios())
|
| 76 |
+
logger.info("Total instruction samples: %d", len(qa_samples))
|
| 77 |
+
|
| 78 |
+
output_path = Path("data/processed/instruction_dataset.jsonl")
|
| 79 |
+
save_instruction_dataset(qa_samples, str(output_path))
|
| 80 |
+
logger.info("Dataset saved: %s", output_path)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
if __name__ == "__main__":
|
| 84 |
+
main()
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.9"
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
api:
|
| 5 |
+
build:
|
| 6 |
+
context: .
|
| 7 |
+
dockerfile: Dockerfile
|
| 8 |
+
env_file:
|
| 9 |
+
- .env
|
| 10 |
+
ports:
|
| 11 |
+
- "8000:8000"
|
| 12 |
+
volumes:
|
| 13 |
+
- ./:/app
|
| 14 |
+
command: uvicorn backend.app.main:app --host 0.0.0.0 --port 8000
|
| 15 |
+
|
| 16 |
+
frontend:
|
| 17 |
+
image: node:20-alpine
|
| 18 |
+
working_dir: /workspace/frontend
|
| 19 |
+
volumes:
|
| 20 |
+
- ./:/workspace
|
| 21 |
+
ports:
|
| 22 |
+
- "5173:5173"
|
| 23 |
+
command: sh -c "npm install && npm run dev -- --host"
|
| 24 |
+
depends_on:
|
| 25 |
+
- api
|
| 26 |
+
|
| 27 |
+
mlflow:
|
| 28 |
+
image: ghcr.io/mlflow/mlflow:v2.22.0
|
| 29 |
+
ports:
|
| 30 |
+
- "5000:5000"
|
| 31 |
+
command: mlflow server --host 0.0.0.0 --port 5000
|
docs/architecture.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Architecture Overview
|
| 2 |
+
|
| 3 |
+
## Components
|
| 4 |
+
|
| 5 |
+
- `worlddisasterlm/` core ML package
|
| 6 |
+
- `backend/` FastAPI inference and risk APIs
|
| 7 |
+
- `frontend/` React disaster command dashboard
|
| 8 |
+
- `scripts/` operational entrypoints
|
| 9 |
+
|
| 10 |
+
## High-Level Flow
|
| 11 |
+
|
| 12 |
+
1. ETL collects and normalizes disaster records
|
| 13 |
+
2. Dataset builder creates instruction JSONL samples
|
| 14 |
+
3. Training pipeline fine-tunes base model using PEFT methods
|
| 15 |
+
4. Evaluation computes safety and quality metrics
|
| 16 |
+
5. Optimization exports ONNX/GGUF variants
|
| 17 |
+
6. API + dashboard deliver inference and analytics to users
|
| 18 |
+
|
| 19 |
+
## Design Goals
|
| 20 |
+
|
| 21 |
+
- Modular and scalable
|
| 22 |
+
- Enterprise-friendly and auditable
|
| 23 |
+
- Beginner-friendly with clear scripts and docs
|
docs/mlops.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# MLOps Guide
|
| 2 |
+
|
| 3 |
+
## Tracking
|
| 4 |
+
|
| 5 |
+
- MLflow: experiment runs, parameters, artifacts
|
| 6 |
+
- Weights & Biases: run dashboards and collaboration
|
| 7 |
+
|
| 8 |
+
Set environment variables in `.env`:
|
| 9 |
+
|
| 10 |
+
- `MLFLOW_TRACKING_URI`
|
| 11 |
+
- `WANDB_PROJECT`
|
| 12 |
+
- `WANDB_ENTITY`
|
| 13 |
+
|
| 14 |
+
## CI/CD
|
| 15 |
+
|
| 16 |
+
GitHub Actions workflows include:
|
| 17 |
+
|
| 18 |
+
- `ci.yml` for lint, test, frontend build
|
| 19 |
+
- `publish-huggingface.yml` for manual release to Hugging Face
|
| 20 |
+
|
| 21 |
+
## Recommended Extensions
|
| 22 |
+
|
| 23 |
+
- Add model regression benchmarks in CI
|
| 24 |
+
- Add vulnerability scans (e.g., Trivy, pip-audit, npm audit)
|
| 25 |
+
- Add staged deployment environments (dev, staging, prod)
|
docs/mobile_deployment.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Mobile Deployment Support
|
| 2 |
+
|
| 3 |
+
WorldDisasterLM mobile support is designed via optimized model artifacts and API-first architecture.
|
| 4 |
+
|
| 5 |
+
## Strategy
|
| 6 |
+
|
| 7 |
+
1. Export compact inference artifacts:
|
| 8 |
+
- GGUF for on-device CPU inference wrappers
|
| 9 |
+
- ONNX for cross-platform runtime support
|
| 10 |
+
|
| 11 |
+
2. Build mobile clients (Android/iOS) that consume API endpoints:
|
| 12 |
+
- `/v1/chat`
|
| 13 |
+
- `/v1/risk/score`
|
| 14 |
+
- `/v1/incidents/classify`
|
| 15 |
+
|
| 16 |
+
3. Optionally embed quantized local models:
|
| 17 |
+
- Android: ONNX Runtime Mobile / llama.cpp bridges
|
| 18 |
+
- iOS: CoreML conversion pipeline or ONNX Runtime
|
| 19 |
+
|
| 20 |
+
## Recommended Runtime Profiles
|
| 21 |
+
|
| 22 |
+
- Edge/Offline mode: GGUF 4-bit quantized variants
|
| 23 |
+
- Connected mode: FastAPI cloud inference with local fallback
|
| 24 |
+
|
| 25 |
+
## Security
|
| 26 |
+
|
| 27 |
+
- Enforce TLS and token-based auth in production
|
| 28 |
+
- Cache only non-sensitive incident summaries
|
| 29 |
+
- Log consent and audit metadata for high-risk guidance usage
|
evaluate.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from dataclasses import asdict
|
| 3 |
+
|
| 4 |
+
from worlddisasterlm.evaluation.metrics import compute_dummy_metrics
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def main() -> None:
|
| 8 |
+
report = compute_dummy_metrics()
|
| 9 |
+
print(json.dumps(asdict(report), indent=2))
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
if __name__ == "__main__":
|
| 13 |
+
main()
|
hf_space/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: WorldDisasterLM-8B
|
| 3 |
+
emoji: 🌍
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: pink
|
| 6 |
+
sdk: gradio
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: true
|
| 9 |
+
license: other
|
| 10 |
+
tags:
|
| 11 |
+
- disaster-management
|
| 12 |
+
- emergency-response
|
| 13 |
+
- humanitarian-ai
|
| 14 |
+
- multilingual
|
| 15 |
+
- fine-tuned
|
| 16 |
+
- qlora
|
| 17 |
+
- text-generation
|
| 18 |
+
short_description: Multilingual disaster guidance in 11 languages
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
# 🌍 WorldDisasterLM-8B
|
| 22 |
+
|
| 23 |
+
**Open Foundation Model for Global Disaster Intelligence**
|
| 24 |
+
|
| 25 |
+
WorldDisasterLM-8B is an instruction-tuned language model built on **Meta Llama 3.1 8B Instruct**,
|
| 26 |
+
domain-adapted on global humanitarian disaster data for emergency guidance, risk assessment, and
|
| 27 |
+
crisis intelligence — across **11 languages**.
|
| 28 |
+
|
| 29 |
+
## Features
|
| 30 |
+
|
| 31 |
+
- 🗣️ **11 Languages** — English, Nepali, Spanish, French, Arabic, Hindi, Telugu, Chinese, Japanese, Korean, Portuguese
|
| 32 |
+
- 🏔️ **Nepal-first** — Nepali (Devanagari) with NDRRMA citations
|
| 33 |
+
- 📊 **Risk Scoring** — Composite disaster risk calculation (vulnerability × exposure)
|
| 34 |
+
- ⚡ **Live Demo** — Ask emergency questions, get actionable guidance instantly
|
| 35 |
+
- 🌐 **Global Coverage** — Earthquakes, floods, cyclones, wildfires, tsunamis, landslides
|
| 36 |
+
|
| 37 |
+
## Training Data Sources
|
| 38 |
+
|
| 39 |
+
| Source | Description |
|
| 40 |
+
|---|---|
|
| 41 |
+
| ReliefWeb | Humanitarian reports and disaster assessments |
|
| 42 |
+
| USGS | Earthquake catalog (M≥4.0, 10-year archive) |
|
| 43 |
+
| NOAA | Weather alerts and severe weather events |
|
| 44 |
+
| GDACS | Global disaster alert coordination events |
|
| 45 |
+
| OpenFEMA | US federal disaster declarations |
|
| 46 |
+
| WHO | Disease outbreak news and public health alerts |
|
| 47 |
+
|
| 48 |
+
## Try It
|
| 49 |
+
|
| 50 |
+
Type any disaster-related question in your language:
|
| 51 |
+
- **English:** "What should I do immediately after an earthquake?"
|
| 52 |
+
- **Nepali:** "भूकम्पको बेला के गर्ने?"
|
| 53 |
+
- **Spanish:** "¿Qué hacer durante una inundación?"
|
| 54 |
+
- **Arabic:** "ما الذي يجب فعله أثناء الإعصار؟"
|
| 55 |
+
|
| 56 |
+
## Safety Notice
|
| 57 |
+
|
| 58 |
+
> ⚠️ This model is for **informational and educational purposes only**.
|
| 59 |
+
> Always follow official emergency orders from local authorities.
|
| 60 |
+
> Do not use as a sole source for life-safety decisions.
|
| 61 |
+
|
| 62 |
+
## Citation
|
| 63 |
+
|
| 64 |
+
```bibtex
|
| 65 |
+
@misc{worlddisasterlm2026,
|
| 66 |
+
title = {WorldDisasterLM: Open Foundation Model for Global Disaster Management},
|
| 67 |
+
year = {2026},
|
| 68 |
+
url = {https://huggingface.co/spaces/YOUR_HF_USERNAME/WorldDisasterLM-8B}
|
| 69 |
+
}
|
| 70 |
+
```
|
hf_space/app.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
WorldDisasterLM-8B — HuggingFace Space (self-contained Gradio demo)
|
| 3 |
+
Runs without GPU. Provides multilingual disaster guidance across 11 languages.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import gradio as gr
|
| 7 |
+
|
| 8 |
+
# ---------------------------------------------------------------------------
|
| 9 |
+
# Multilingual guidance constants
|
| 10 |
+
# ---------------------------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
NEPALI_GUIDANCE = (
|
| 13 |
+
"आपतकालीन प्रतिक्रियाको सुझावकात विधिहरू: "
|
| 14 |
+
"तत्काल खतरा मूल्याङ्कन गर्नुहोस्, "
|
| 15 |
+
"सुरक्षित ठाउँमा जानुहोस्, "
|
| 16 |
+
"आपतकालीन सेवा (१०१ / १०२) मा फोन गर्नुहोस्, "
|
| 17 |
+
"कमजोर वर्गको सुरक्षा गर्नुहोस्, "
|
| 18 |
+
"र हर १५ मिनेटमा आधिकारिक सूचना अनुसरण गर्नुहोस्।"
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
SPANISH_GUIDANCE = (
|
| 22 |
+
"Pasos recomendados de respuesta de emergencia: "
|
| 23 |
+
"evalúe los peligros inmediatos, desplácese a un lugar seguro, "
|
| 24 |
+
"llame a los servicios de emergencia, proteja a los grupos vulnerables "
|
| 25 |
+
"y verifique las alertas oficiales cada 15 minutos."
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
FRENCH_GUIDANCE = (
|
| 29 |
+
"Étapes recommandées de réponse d'urgence: "
|
| 30 |
+
"évaluez les dangers immédiats, déplacez-vous vers un endroit sûr, "
|
| 31 |
+
"appelez les services d'urgence, protégez les groupes vulnérables "
|
| 32 |
+
"et vérifiez les alertes officielles toutes les 15 minutes."
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
ARABIC_GUIDANCE = (
|
| 36 |
+
"خطوات الاستجابة للطوارئ الموصى بها: "
|
| 37 |
+
"تقييم المخاطر الفورية، والانتقال إلى مكان آمن، "
|
| 38 |
+
"والاتصال بخدمات الطوارئ، وحماية الفئات الضعيفة، "
|
| 39 |
+
"والتحقق من التنبيهات الرسمية كل 15 دقيقة."
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
HINDI_GUIDANCE = (
|
| 43 |
+
"अनुशंसित आपातकालीन प्रतिक्रिया चरण: "
|
| 44 |
+
"तत्काल खतरों का मूल्यांकन करें, सुरक्षित स्थान पर जाएं, "
|
| 45 |
+
"आपातकालीन सेवाओं को कॉल करें, कमजोर समूहों की रक्षा करें "
|
| 46 |
+
"और हर 15 मिनट में आधिकारिक अलर्ट की जांच करें।"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
TELUGU_GUIDANCE = (
|
| 50 |
+
"సిఫారసు చేయబడిన అత్యవసర ప్రతిస్పందన దశలు: "
|
| 51 |
+
"తక్షణ ప్రమాదాలను అంచనా వేయండి, సురక్షిత స్థలానికి వెళ్ళండి, "
|
| 52 |
+
"అత్యవసర సేవలకు కాల్ చేయండి, హాని కలిగించే సమూహాలను రక్షించండి "
|
| 53 |
+
"మరియు ప్రతి 15 నిమిషాలకు అధికారిక హెచ్చరికలను తనిఖీ చేయండి."
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
CHINESE_GUIDANCE = (
|
| 57 |
+
"建议的紧急响应步骤:评估直接危险,转移到安全地点,"
|
| 58 |
+
"拨打紧急服务电话,保护弱势群体,"
|
| 59 |
+
"并每15分钟核实官方警报。"
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
JAPANESE_GUIDANCE = (
|
| 63 |
+
"推奨される緊急対応手順:直接的な危険を評価し、安全な場所に移動し、"
|
| 64 |
+
"緊急サービスに電話し、脆弱なグループを保護し、"
|
| 65 |
+
"15分ごとに公式アラートを確認してください。"
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
KOREAN_GUIDANCE = (
|
| 69 |
+
"권장 비상 대응 단계: 즉각적인 위험을 평가하고, 안전한 장소로 이동하고, "
|
| 70 |
+
"긴급 서비스에 전화하고, 취약 계층을 보호하고, "
|
| 71 |
+
"15분마다 공식 경보를 확인하십시오."
|
| 72 |
+
)
|
| 73 |
+
|
| 74 |
+
PORTUGUESE_GUIDANCE = (
|
| 75 |
+
"Etapas recomendadas de resposta de emergência: "
|
| 76 |
+
"avalie os perigos imediatos, mova-se para um local seguro, "
|
| 77 |
+
"ligue para os serviços de emergência, proteja os grupos vulneráveis "
|
| 78 |
+
"e verifique os alertas oficiais a cada 15 minutos."
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
LANGUAGE_GUIDANCE = {
|
| 82 |
+
"english": ("English", "Recommended next steps: assess immediate hazards, move to a safe location, call emergency services, protect vulnerable groups, and verify updates from official alerts every 15 minutes.", ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 83 |
+
"nepali": ("नेपाली", NEPALI_GUIDANCE, ["NDRRMA नेपाल विपद् व्यवस्थापन प्राधिकरण", "WHO आपतकालीन प्रतिक्रिया मार्गदर्शन", "UNDRR Sendai Framework 2015-2030"]),
|
| 84 |
+
"ne": ("नेपाली", NEPALI_GUIDANCE, ["NDRRMA नेपाल विपद् व्यवस्थापन प्राधिकरण", "WHO आपतकाली��� प्रतिक्रिया मार्गदर्शन", "UNDRR Sendai Framework 2015-2030"]),
|
| 85 |
+
"नेपाली": ("नेपाली", NEPALI_GUIDANCE, ["NDRRMA नेपाल विपद् व्यवस्थापन प्राधिकरण", "WHO आपतकालीन प्रतिक्रिया मार्गदर्शन", "UNDRR Sendai Framework 2015-2030"]),
|
| 86 |
+
"spanish": ("Spanish", SPANISH_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 87 |
+
"french": ("French", FRENCH_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 88 |
+
"arabic": ("Arabic", ARABIC_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 89 |
+
"hindi": ("Hindi", HINDI_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 90 |
+
"telugu": ("Telugu", TELUGU_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 91 |
+
"chinese": ("Chinese", CHINESE_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 92 |
+
"japanese": ("Japanese", JAPANESE_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 93 |
+
"korean": ("Korean", KOREAN_GUIDANCE, ["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 94 |
+
"portuguese": ("Portuguese", PORTUGUESE_GUIDANCE,["UNDRR preparedness guidelines", "WHO emergency response guidance"]),
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
LANGUAGES = [
|
| 98 |
+
"English", "Nepali", "Spanish", "French", "Arabic",
|
| 99 |
+
"Hindi", "Telugu", "Chinese", "Japanese", "Korean", "Portuguese",
|
| 100 |
+
]
|
| 101 |
+
|
| 102 |
+
RISK_LEVELS = {
|
| 103 |
+
(0.0, 0.3): ("low", "Continue monitoring; no immediate action required."),
|
| 104 |
+
(0.3, 0.5): ("moderate", "Activate preparedness protocols and standby teams."),
|
| 105 |
+
(0.5, 0.7): ("high", "Deploy response teams and issue public advisories."),
|
| 106 |
+
(0.7, 0.85): ("severe", "Mobilize full emergency response and evacuation support."),
|
| 107 |
+
(0.85, 1.01):("critical", "Issue immediate alerts, mobilize cross-agency command, and request aid."),
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def _risk_level(score: float) -> tuple[str, str]:
|
| 112 |
+
for (lo, hi), (level, rec) in RISK_LEVELS.items():
|
| 113 |
+
if lo <= score < hi:
|
| 114 |
+
return level, rec
|
| 115 |
+
return "critical", "Issue immediate alerts."
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def chat_response(message: str, language: str, region: str) -> str:
|
| 119 |
+
if not message.strip():
|
| 120 |
+
return "Please enter an emergency query."
|
| 121 |
+
lang_key = language.strip().lower()
|
| 122 |
+
label, guidance, citations = LANGUAGE_GUIDANCE.get(
|
| 123 |
+
lang_key,
|
| 124 |
+
LANGUAGE_GUIDANCE["english"],
|
| 125 |
+
)
|
| 126 |
+
answer = f"[WorldDisasterLM-8B | {label} | {region}] {guidance}"
|
| 127 |
+
cite_str = "\n".join(f" • {c}" for c in citations)
|
| 128 |
+
return f"{answer}\n\n**Sources:**\n{cite_str}"
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def risk_score(region: str, hazard: str, vulnerability: float, exposure: float) -> str:
|
| 132 |
+
score = round(min((vulnerability * 0.5 + exposure * 0.5) * 1.1, 1.0), 3)
|
| 133 |
+
level, rec = _risk_level(score)
|
| 134 |
+
return (
|
| 135 |
+
f"**Risk Score:** {score}\n"
|
| 136 |
+
f"**Risk Level:** {level.upper()}\n"
|
| 137 |
+
f"**Region:** {region} | **Hazard:** {hazard}\n\n"
|
| 138 |
+
f"**Recommendation:** {rec}"
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
# ---------------------------------------------------------------------------
|
| 143 |
+
# Gradio UI
|
| 144 |
+
# ---------------------------------------------------------------------------
|
| 145 |
+
|
| 146 |
+
with gr.Blocks(title="WorldDisasterLM-8B") as demo:
|
| 147 |
+
gr.Markdown(
|
| 148 |
+
"""
|
| 149 |
+
# 🌍 WorldDisasterLM-8B
|
| 150 |
+
### Open Foundation Model for Global Disaster Intelligence
|
| 151 |
+
|
| 152 |
+
Multilingual emergency guidance powered by **WorldDisasterLM-8B** — fine-tuned on humanitarian data
|
| 153 |
+
from ReliefWeb, USGS, NOAA, GDACS, OpenFEMA, and WHO.
|
| 154 |
+
|
| 155 |
+
> ⚠️ **For informational purposes only.** Always follow official emergency orders from local authorities.
|
| 156 |
+
"""
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
with gr.Tabs():
|
| 160 |
+
# --- Chat Tab ---
|
| 161 |
+
with gr.Tab("💬 Emergency Guidance"):
|
| 162 |
+
with gr.Row():
|
| 163 |
+
with gr.Column(scale=2):
|
| 164 |
+
query = gr.Textbox(
|
| 165 |
+
label="Emergency Query",
|
| 166 |
+
placeholder="e.g. What to do during an earthquake? / भूकम्पको बेला के गर्ने?",
|
| 167 |
+
lines=3,
|
| 168 |
+
)
|
| 169 |
+
with gr.Column(scale=1):
|
| 170 |
+
lang = gr.Dropdown(LANGUAGES, value="English", label="Language")
|
| 171 |
+
region_in = gr.Textbox(value="global", label="Region / Country")
|
| 172 |
+
|
| 173 |
+
chat_btn = gr.Button("Get Guidance", variant="primary")
|
| 174 |
+
chat_out = gr.Markdown(label="Response")
|
| 175 |
+
|
| 176 |
+
chat_btn.click(
|
| 177 |
+
fn=chat_response,
|
| 178 |
+
inputs=[query, lang, region_in],
|
| 179 |
+
outputs=chat_out,
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
gr.Examples(
|
| 183 |
+
examples=[
|
| 184 |
+
["What should I do immediately after an earthquake?", "English", "Nepal"],
|
| 185 |
+
["भूकम्पको ��ेला के गर्ने?", "Nepali", "Nepal"],
|
| 186 |
+
["¿Qué hacer durante una inundación?", "Spanish", "Colombia"],
|
| 187 |
+
["홍수 때 어떻게 해야 합니까?", "Korean", "South Korea"],
|
| 188 |
+
["台风来临时应该怎么做?", "Chinese", "China"],
|
| 189 |
+
["What are signs of an imminent landslide?", "English", "Philippines"],
|
| 190 |
+
],
|
| 191 |
+
inputs=[query, lang, region_in],
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
# --- Risk Score Tab ---
|
| 195 |
+
with gr.Tab("📊 Risk Assessment"):
|
| 196 |
+
gr.Markdown("Calculate composite disaster risk score for any region.")
|
| 197 |
+
with gr.Row():
|
| 198 |
+
rs_region = gr.Textbox(value="Nepal", label="Region")
|
| 199 |
+
rs_hazard = gr.Dropdown(
|
| 200 |
+
["earthquake", "flood", "cyclone", "wildfire", "drought", "tsunami", "landslide", "volcano"],
|
| 201 |
+
value="earthquake",
|
| 202 |
+
label="Hazard Type",
|
| 203 |
+
)
|
| 204 |
+
with gr.Row():
|
| 205 |
+
rs_vuln = gr.Slider(0, 1, value=0.7, step=0.01, label="Vulnerability Index (0–1)")
|
| 206 |
+
rs_exp = gr.Slider(0, 1, value=0.8, step=0.01, label="Exposure Index (0–1)")
|
| 207 |
+
|
| 208 |
+
rs_btn = gr.Button("Calculate Risk Score", variant="primary")
|
| 209 |
+
rs_out = gr.Markdown()
|
| 210 |
+
|
| 211 |
+
rs_btn.click(
|
| 212 |
+
fn=risk_score,
|
| 213 |
+
inputs=[rs_region, rs_hazard, rs_vuln, rs_exp],
|
| 214 |
+
outputs=rs_out,
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
# --- About Tab ---
|
| 218 |
+
with gr.Tab("ℹ️ About"):
|
| 219 |
+
gr.Markdown(
|
| 220 |
+
"""
|
| 221 |
+
## About WorldDisasterLM-8B
|
| 222 |
+
|
| 223 |
+
**WorldDisasterLM-8B** is an instruction-tuned language model built on Meta's Llama 3.1 8B Instruct,
|
| 224 |
+
domain-adapted for global disaster management and humanitarian response.
|
| 225 |
+
|
| 226 |
+
### Supported Languages
|
| 227 |
+
| Language | Script | ISO |
|
| 228 |
+
|---|---|---|
|
| 229 |
+
| English | Latin | en |
|
| 230 |
+
| Nepali | Devanagari | ne |
|
| 231 |
+
| Spanish | Latin | es |
|
| 232 |
+
| French | Latin | fr |
|
| 233 |
+
| Arabic | Arabic | ar |
|
| 234 |
+
| Hindi | Devanagari | hi |
|
| 235 |
+
| Telugu | Telugu | te |
|
| 236 |
+
| Chinese | Simplified Han | zh |
|
| 237 |
+
| Japanese | Kanji/Hiragana | ja |
|
| 238 |
+
| Korean | Hangul | ko |
|
| 239 |
+
| Portuguese | Latin | pt |
|
| 240 |
+
|
| 241 |
+
### Training Data Sources
|
| 242 |
+
- **ReliefWeb** — Humanitarian reports and disaster assessments
|
| 243 |
+
- **USGS** — Earthquake catalog (M≥4.0, 10-year archive)
|
| 244 |
+
- **NOAA** — Weather alerts and severe weather events
|
| 245 |
+
- **GDACS** — Global disaster alert coordination
|
| 246 |
+
- **OpenFEMA** — US federal disaster declarations
|
| 247 |
+
- **WHO** — Disease outbreak and public health alerts
|
| 248 |
+
|
| 249 |
+
### Training Method
|
| 250 |
+
QLoRA fine-tuning (4-bit NF4 quantization, LoRA r=16) on Llama 3.1 8B Instruct.
|
| 251 |
+
|
| 252 |
+
### Citation
|
| 253 |
+
```
|
| 254 |
+
@misc{worlddisasterlm2026,
|
| 255 |
+
title = {WorldDisasterLM: Open Foundation Model for Global Disaster Management},
|
| 256 |
+
year = {2026}
|
| 257 |
+
}
|
| 258 |
+
```
|
| 259 |
+
|
| 260 |
+
### License
|
| 261 |
+
This demo is released under the [Llama 3 Community License](https://llama.meta.com/llama3/license/).
|
| 262 |
+
"""
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
if __name__ == "__main__":
|
| 266 |
+
demo.launch()
|
hf_space/requirements.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0,<5.0.0
|
inference.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from backend.app.models.schemas import ChatMessage
|
| 4 |
+
from backend.app.services.inference_service import generate_response
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def parse_args() -> argparse.Namespace:
|
| 8 |
+
parser = argparse.ArgumentParser(description="Run local WorldDisasterLM-8B inference")
|
| 9 |
+
parser.add_argument("--prompt", required=True, help="User query")
|
| 10 |
+
parser.add_argument("--language", default="English")
|
| 11 |
+
parser.add_argument("--region", default="global")
|
| 12 |
+
return parser.parse_args()
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def main() -> None:
|
| 16 |
+
args = parse_args()
|
| 17 |
+
response = generate_response(
|
| 18 |
+
[ChatMessage(role="user", content=args.prompt)],
|
| 19 |
+
language=args.language,
|
| 20 |
+
region=args.region,
|
| 21 |
+
)
|
| 22 |
+
print(response.answer)
|
| 23 |
+
print(f"confidence={response.confidence} needs_human_review={response.needs_human_review}")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
if __name__ == "__main__":
|
| 27 |
+
main()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=68", "wheel"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "worlddisasterlm"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "Open foundation model stack for global disaster management"
|
| 9 |
+
readme = "README.md"
|
| 10 |
+
requires-python = ">=3.10"
|
| 11 |
+
license = { text = "MIT" }
|
| 12 |
+
authors = [{ name = "WorldDisasterLM Contributors" }]
|
| 13 |
+
dependencies = []
|
| 14 |
+
|
| 15 |
+
[tool.ruff]
|
| 16 |
+
line-length = 100
|
| 17 |
+
target-version = "py310"
|
| 18 |
+
|
| 19 |
+
[tool.pytest.ini_options]
|
| 20 |
+
testpaths = ["tests"]
|
| 21 |
+
pythonpath = ["."]
|
requirements.txt
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ── Web & API ──────────────────────────────────────────────────────────────
|
| 2 |
+
fastapi==0.115.12
|
| 3 |
+
uvicorn[standard]==0.34.2
|
| 4 |
+
pydantic==2.11.5
|
| 5 |
+
pydantic-settings==2.9.1
|
| 6 |
+
httpx==0.28.1
|
| 7 |
+
python-dotenv==1.0.1
|
| 8 |
+
|
| 9 |
+
# ── Data collection ─────────────────────────────────────────────────────────
|
| 10 |
+
feedparser==6.0.11
|
| 11 |
+
beautifulsoup4==4.12.3
|
| 12 |
+
lxml==5.3.0
|
| 13 |
+
|
| 14 |
+
# ── Data processing ─────────────────────────────────────────────────────────
|
| 15 |
+
pandas==2.2.3
|
| 16 |
+
numpy==2.2.6
|
| 17 |
+
scikit-learn==1.6.1
|
| 18 |
+
datasets==3.6.0
|
| 19 |
+
|
| 20 |
+
# ── ML core (install CUDA wheel separately for GPU training) ─────────────────
|
| 21 |
+
transformers==4.53.0
|
| 22 |
+
accelerate==1.7.0
|
| 23 |
+
peft==0.15.2
|
| 24 |
+
trl==0.9.6
|
| 25 |
+
bitsandbytes==0.45.5
|
| 26 |
+
# torch — install manually for your CUDA version:
|
| 27 |
+
# pip install torch --index-url https://download.pytorch.org/whl/cu124
|
| 28 |
+
|
| 29 |
+
# ── Distributed training (optional) ─────────────────────────────────────────
|
| 30 |
+
# deepspeed==0.16.7 # Linux/CUDA only — uncomment if using DeepSpeed
|
| 31 |
+
|
| 32 |
+
# ── Evaluation & export ──────────────────────────────────────────────────────
|
| 33 |
+
evaluate==0.4.3
|
| 34 |
+
sacrebleu==2.5.1
|
| 35 |
+
rouge-score==0.1.2
|
| 36 |
+
sentencepiece==0.2.0
|
| 37 |
+
onnx==1.17.0
|
| 38 |
+
onnxruntime==1.22.0
|
| 39 |
+
|
| 40 |
+
# ── HuggingFace publishing ───────────────────────────────────────────────────
|
| 41 |
+
huggingface_hub==0.30.2
|
| 42 |
+
|
| 43 |
+
# ── Demo & MLOps ─────────────────────────────────────────────────────────────
|
| 44 |
+
gradio==5.33.0
|
| 45 |
+
mlflow==2.22.0
|
| 46 |
+
wandb==0.19.11
|
| 47 |
+
|
| 48 |
+
# ── Dev & testing ────────────────────────────────────────────────────────────
|
| 49 |
+
pytest==8.3.5
|
| 50 |
+
pytest-asyncio==0.26.0
|
| 51 |
+
ruff==0.11.11
|
scripts/collect_data.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Data collection orchestrator.
|
| 3 |
+
|
| 4 |
+
Runs all online collectors and saves the combined raw dataset plus
|
| 5 |
+
the instruction-following JSONL used for training.
|
| 6 |
+
|
| 7 |
+
Usage
|
| 8 |
+
-----
|
| 9 |
+
python scripts/collect_data.py # all sources, default limits
|
| 10 |
+
python scripts/collect_data.py --sources reliefweb usgs gdacs
|
| 11 |
+
python scripts/collect_data.py --max-per-source 2000
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
import json
|
| 18 |
+
import logging
|
| 19 |
+
import time
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
DEFAULT_LIMITS: dict[str, int] = {
|
| 25 |
+
"reliefweb": 5000,
|
| 26 |
+
"usgs": 20000,
|
| 27 |
+
"gdacs": 2000,
|
| 28 |
+
"noaa": 5000,
|
| 29 |
+
"openfema": 20000,
|
| 30 |
+
"who": 1000,
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def parse_args() -> argparse.Namespace:
|
| 35 |
+
parser = argparse.ArgumentParser(description="Collect online disaster data")
|
| 36 |
+
parser.add_argument(
|
| 37 |
+
"--sources",
|
| 38 |
+
nargs="+",
|
| 39 |
+
default=list(DEFAULT_LIMITS.keys()),
|
| 40 |
+
choices=list(DEFAULT_LIMITS.keys()),
|
| 41 |
+
help="Data sources to collect from",
|
| 42 |
+
)
|
| 43 |
+
parser.add_argument("--max-per-source", type=int, default=None, help="Override max records per source")
|
| 44 |
+
parser.add_argument("--raw-dir", default="data/raw", help="Directory for raw records")
|
| 45 |
+
parser.add_argument("--processed-dir", default="data/processed", help="Directory for processed JSONL")
|
| 46 |
+
return parser.parse_args()
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def save_records(records: list, path: Path) -> None:
|
| 50 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 51 |
+
with path.open("w", encoding="utf-8") as handle:
|
| 52 |
+
for record in records:
|
| 53 |
+
handle.write(json.dumps(record.__dict__, ensure_ascii=False) + "\n")
|
| 54 |
+
logger.info("Saved %d records to %s", len(records), path)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def collect_source(source: str, max_records: int) -> list:
|
| 58 |
+
if source == "reliefweb":
|
| 59 |
+
from worlddisasterlm.data.collectors.reliefweb import collect_reliefweb
|
| 60 |
+
return collect_reliefweb(max_records=max_records)
|
| 61 |
+
if source == "usgs":
|
| 62 |
+
from worlddisasterlm.data.collectors.usgs import collect_usgs
|
| 63 |
+
return collect_usgs(max_records=max_records)
|
| 64 |
+
if source == "gdacs":
|
| 65 |
+
from worlddisasterlm.data.collectors.gdacs import collect_gdacs
|
| 66 |
+
return collect_gdacs(max_records=max_records)
|
| 67 |
+
if source == "noaa":
|
| 68 |
+
from worlddisasterlm.data.collectors.noaa import collect_noaa
|
| 69 |
+
return collect_noaa(max_records=max_records)
|
| 70 |
+
if source == "openfema":
|
| 71 |
+
from worlddisasterlm.data.collectors.openfema import collect_openfema
|
| 72 |
+
return collect_openfema(max_records=max_records)
|
| 73 |
+
if source == "who":
|
| 74 |
+
from worlddisasterlm.data.collectors.who_rss import collect_who
|
| 75 |
+
return collect_who(max_records=max_records)
|
| 76 |
+
raise ValueError(f"Unknown source: {source}")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def main() -> None:
|
| 80 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 81 |
+
args = parse_args()
|
| 82 |
+
|
| 83 |
+
from worlddisasterlm.data.etl import DisasterETL
|
| 84 |
+
from worlddisasterlm.data.qa_generator import generate_qa_pairs
|
| 85 |
+
from worlddisasterlm.data.scenario_builder import build_all_scenarios
|
| 86 |
+
from worlddisasterlm.data.processors import save_instruction_dataset
|
| 87 |
+
|
| 88 |
+
raw_dir = Path(args.raw_dir)
|
| 89 |
+
processed_dir = Path(args.processed_dir)
|
| 90 |
+
processed_dir.mkdir(parents=True, exist_ok=True)
|
| 91 |
+
|
| 92 |
+
all_records = []
|
| 93 |
+
|
| 94 |
+
for source in args.sources:
|
| 95 |
+
limit = args.max_per_source or DEFAULT_LIMITS.get(source, 5000)
|
| 96 |
+
logger.info("Collecting from %s (max=%d) …", source, limit)
|
| 97 |
+
try:
|
| 98 |
+
records = collect_source(source, limit)
|
| 99 |
+
save_records(records, raw_dir / f"{source}.jsonl")
|
| 100 |
+
all_records.extend(records)
|
| 101 |
+
except Exception as exc:
|
| 102 |
+
logger.error("Failed to collect from %s: %s", source, exc)
|
| 103 |
+
|
| 104 |
+
time.sleep(1) # polite delay between sources
|
| 105 |
+
|
| 106 |
+
etl = DisasterETL()
|
| 107 |
+
all_records = etl.deduplicate(all_records)
|
| 108 |
+
all_records = etl.normalize(all_records)
|
| 109 |
+
logger.info("Total normalized records after dedup: %d", len(all_records))
|
| 110 |
+
|
| 111 |
+
# Generate instruction QA pairs (8x amplification)
|
| 112 |
+
logger.info("Generating instruction QA pairs …")
|
| 113 |
+
qa_samples = generate_qa_pairs(all_records)
|
| 114 |
+
|
| 115 |
+
# Add compound + multilingual scenarios
|
| 116 |
+
extra_samples = build_all_scenarios()
|
| 117 |
+
qa_samples.extend(extra_samples)
|
| 118 |
+
|
| 119 |
+
logger.info("Total instruction samples: %d", len(qa_samples))
|
| 120 |
+
|
| 121 |
+
output_path = processed_dir / "instruction_dataset.jsonl"
|
| 122 |
+
save_instruction_dataset(qa_samples, str(output_path))
|
| 123 |
+
logger.info("Instruction dataset saved: %s", output_path)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
main()
|
scripts/convert_gguf.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
GGUF conversion script for WorldDisasterLM.
|
| 3 |
+
|
| 4 |
+
Converts the merged Hugging Face model to GGUF format for CPU inference
|
| 5 |
+
and mobile deployment using llama.cpp.
|
| 6 |
+
|
| 7 |
+
Quantization sizes (approximate for 8B model)
|
| 8 |
+
-----------------------------------------------
|
| 9 |
+
Q4_K_M → ~4.8 GB (recommended for most use cases)
|
| 10 |
+
Q5_K_M → ~5.6 GB (better quality)
|
| 11 |
+
Q8_0 → ~8.5 GB (highest quality, slower)
|
| 12 |
+
f16 → ~15 GB (full precision)
|
| 13 |
+
|
| 14 |
+
Usage
|
| 15 |
+
-----
|
| 16 |
+
# Full automated flow (requires llama.cpp cloned alongside this repo)
|
| 17 |
+
python scripts/convert_gguf.py \\
|
| 18 |
+
--model-path checkpoints/worlddisasterlm-merged \\
|
| 19 |
+
--llama-cpp-path ../llama.cpp \\
|
| 20 |
+
--quant Q4_K_M
|
| 21 |
+
|
| 22 |
+
# Manual steps are printed if llama.cpp is not found
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import argparse
|
| 28 |
+
import logging
|
| 29 |
+
import shutil
|
| 30 |
+
import subprocess
|
| 31 |
+
import sys
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
|
| 34 |
+
logger = logging.getLogger(__name__)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def parse_args() -> argparse.Namespace:
|
| 38 |
+
parser = argparse.ArgumentParser(description="Convert WorldDisasterLM to GGUF")
|
| 39 |
+
parser.add_argument("--model-path", default="checkpoints/worlddisasterlm-merged", help="Path to merged HF model")
|
| 40 |
+
parser.add_argument("--output-dir", default="artifacts", help="Output directory for GGUF files")
|
| 41 |
+
parser.add_argument("--llama-cpp-path", default="../llama.cpp", help="Path to llama.cpp repo")
|
| 42 |
+
parser.add_argument(
|
| 43 |
+
"--quant",
|
| 44 |
+
default="Q4_K_M",
|
| 45 |
+
choices=["Q4_K_M", "Q5_K_M", "Q8_0", "f16"],
|
| 46 |
+
help="Quantization type",
|
| 47 |
+
)
|
| 48 |
+
return parser.parse_args()
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def print_manual_steps(model_path: str, output_dir: str, quant: str) -> None:
|
| 52 |
+
print("\n" + "=" * 70)
|
| 53 |
+
print("MANUAL GGUF CONVERSION STEPS")
|
| 54 |
+
print("=" * 70)
|
| 55 |
+
print("\nStep 1: Clone llama.cpp and build")
|
| 56 |
+
print(" git clone https://github.com/ggerganov/llama.cpp")
|
| 57 |
+
print(" cd llama.cpp")
|
| 58 |
+
print(" cmake -B build -DLLAMA_BLAS=ON -DLLAMA_BLAS_VENDOR=OpenBLAS")
|
| 59 |
+
print(" cmake --build build --config Release")
|
| 60 |
+
print()
|
| 61 |
+
print("Step 2: Install Python dependencies")
|
| 62 |
+
print(" pip install -r llama.cpp/requirements.txt")
|
| 63 |
+
print()
|
| 64 |
+
print("Step 3: Convert HF model to GGUF (f16)")
|
| 65 |
+
print(f" python llama.cpp/convert_hf_to_gguf.py {model_path} \\")
|
| 66 |
+
print(f" --outtype f16 --outfile {output_dir}/worlddisasterlm_f16.gguf")
|
| 67 |
+
print()
|
| 68 |
+
print(f"Step 4: Quantize to {quant}")
|
| 69 |
+
print(f" ./llama.cpp/build/bin/llama-quantize \\")
|
| 70 |
+
print(f" {output_dir}/worlddisasterlm_f16.gguf \\")
|
| 71 |
+
print(f" {output_dir}/worlddisasterlm_{quant.lower()}.gguf \\")
|
| 72 |
+
print(f" {quant}")
|
| 73 |
+
print()
|
| 74 |
+
print("Step 5: Upload GGUF to Hugging Face")
|
| 75 |
+
print(" huggingface-cli upload YourUsername/WorldDisasterLM-GGUF \\")
|
| 76 |
+
print(f" {output_dir}/worlddisasterlm_{quant.lower()}.gguf \\")
|
| 77 |
+
print(f" worlddisasterlm_{quant.lower()}.gguf")
|
| 78 |
+
print("=" * 70 + "\n")
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def run_conversion(model_path: str, llama_cpp_path: str, output_dir: str, quant: str) -> None:
|
| 82 |
+
llama_dir = Path(llama_cpp_path).resolve()
|
| 83 |
+
model_dir = Path(model_path).resolve()
|
| 84 |
+
out_dir = Path(output_dir)
|
| 85 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 86 |
+
|
| 87 |
+
convert_script = llama_dir / "convert_hf_to_gguf.py"
|
| 88 |
+
quantize_bin = llama_dir / "build" / "bin" / "llama-quantize"
|
| 89 |
+
quantize_bin_win = llama_dir / "build" / "bin" / "Release" / "llama-quantize.exe"
|
| 90 |
+
|
| 91 |
+
if not convert_script.exists():
|
| 92 |
+
logger.error("convert_hf_to_gguf.py not found in %s", llama_dir)
|
| 93 |
+
print_manual_steps(model_path, output_dir, quant)
|
| 94 |
+
sys.exit(1)
|
| 95 |
+
|
| 96 |
+
f16_gguf = out_dir / "worlddisasterlm_f16.gguf"
|
| 97 |
+
quant_gguf = out_dir / f"worlddisasterlm_{quant.lower()}.gguf"
|
| 98 |
+
|
| 99 |
+
# Convert to f16 GGUF
|
| 100 |
+
logger.info("Converting HF model to f16 GGUF …")
|
| 101 |
+
subprocess.run(
|
| 102 |
+
[sys.executable, str(convert_script), str(model_dir), "--outtype", "f16", "--outfile", str(f16_gguf)],
|
| 103 |
+
check=True,
|
| 104 |
+
)
|
| 105 |
+
|
| 106 |
+
# Find quantize binary
|
| 107 |
+
q_bin = quantize_bin if quantize_bin.exists() else (quantize_bin_win if quantize_bin_win.exists() else None)
|
| 108 |
+
if q_bin is None:
|
| 109 |
+
logger.warning("llama-quantize binary not found. f16 GGUF saved at %s", f16_gguf)
|
| 110 |
+
print_manual_steps(model_path, output_dir, quant)
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
# Quantize
|
| 114 |
+
logger.info("Quantizing to %s …", quant)
|
| 115 |
+
subprocess.run([str(q_bin), str(f16_gguf), str(quant_gguf), quant], check=True)
|
| 116 |
+
logger.info("GGUF model saved to %s", quant_gguf)
|
| 117 |
+
logger.info("Upload with: huggingface-cli upload <repo_id> %s", quant_gguf)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def main() -> None:
|
| 121 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 122 |
+
args = parse_args()
|
| 123 |
+
|
| 124 |
+
llama_dir = Path(args.llama_cpp_path)
|
| 125 |
+
if not llama_dir.exists():
|
| 126 |
+
logger.warning("llama.cpp directory not found at %s — printing manual steps.", args.llama_cpp_path)
|
| 127 |
+
print_manual_steps(args.model_path, args.output_dir, args.quant)
|
| 128 |
+
return
|
| 129 |
+
|
| 130 |
+
run_conversion(
|
| 131 |
+
model_path=args.model_path,
|
| 132 |
+
llama_cpp_path=args.llama_cpp_path,
|
| 133 |
+
output_dir=args.output_dir,
|
| 134 |
+
quant=args.quant,
|
| 135 |
+
)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
main()
|
scripts/export_gguf.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from worlddisasterlm.optimization.export_gguf import export_gguf
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def parse_args() -> argparse.Namespace:
|
| 7 |
+
parser = argparse.ArgumentParser(description="Export WorldDisasterLM-8B to GGUF")
|
| 8 |
+
parser.add_argument("--model-path", default="checkpoints/worlddisasterlm-8b-qlora")
|
| 9 |
+
parser.add_argument("--output-path", default="artifacts/worlddisasterlm-8b.gguf")
|
| 10 |
+
return parser.parse_args()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def main() -> None:
|
| 14 |
+
args = parse_args()
|
| 15 |
+
export_gguf(model_path=args.model_path, output_path=args.output_path)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
if __name__ == "__main__":
|
| 19 |
+
main()
|
scripts/export_onnx.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
|
| 3 |
+
from worlddisasterlm.optimization.export_onnx import export_onnx
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def parse_args() -> argparse.Namespace:
|
| 7 |
+
parser = argparse.ArgumentParser(description="Export WorldDisasterLM-8B to ONNX")
|
| 8 |
+
parser.add_argument("--model-path", default="checkpoints/worlddisasterlm-8b-qlora")
|
| 9 |
+
parser.add_argument("--output-path", default="artifacts/worlddisasterlm-8b.onnx")
|
| 10 |
+
return parser.parse_args()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def main() -> None:
|
| 14 |
+
args = parse_args()
|
| 15 |
+
export_onnx(model_path=args.model_path, output_path=args.output_path)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
if __name__ == "__main__":
|
| 19 |
+
main()
|
scripts/push_to_hub.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face Hub push script.
|
| 3 |
+
|
| 4 |
+
Merges LoRA adapters into the base model, creates the model card, and
|
| 5 |
+
pushes everything to the Hub under your account namespace.
|
| 6 |
+
|
| 7 |
+
Usage
|
| 8 |
+
-----
|
| 9 |
+
python scripts/push_to_hub.py \\
|
| 10 |
+
--adapter checkpoints/worlddisasterlm-qlora \\
|
| 11 |
+
--base-model meta-llama/Llama-3.1-8B-Instruct \\
|
| 12 |
+
--repo-id YourHFUsername/WorldDisasterLM-8B
|
| 13 |
+
|
| 14 |
+
Requirements
|
| 15 |
+
------------
|
| 16 |
+
export HF_TOKEN=hf_xxxx
|
| 17 |
+
pip install transformers peft huggingface_hub
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import logging
|
| 24 |
+
import os
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
import tempfile
|
| 27 |
+
|
| 28 |
+
logger = logging.getLogger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
HF_MODEL_CARD = """---
|
| 32 |
+
language:
|
| 33 |
+
- en
|
| 34 |
+
- ne
|
| 35 |
+
- es
|
| 36 |
+
- fr
|
| 37 |
+
- ar
|
| 38 |
+
- hi
|
| 39 |
+
- te
|
| 40 |
+
- zh
|
| 41 |
+
- ja
|
| 42 |
+
- ko
|
| 43 |
+
- pt
|
| 44 |
+
license: llama3
|
| 45 |
+
base_model: meta-llama/Llama-3.1-8B-Instruct
|
| 46 |
+
tags:
|
| 47 |
+
- disaster-management
|
| 48 |
+
- emergency-response
|
| 49 |
+
- humanitarian-ai
|
| 50 |
+
- fine-tuned
|
| 51 |
+
- qlora
|
| 52 |
+
- lora
|
| 53 |
+
- peft
|
| 54 |
+
pipeline_tag: text-generation
|
| 55 |
+
library_name: transformers
|
| 56 |
+
model-index:
|
| 57 |
+
- name: WorldDisasterLM-8B
|
| 58 |
+
results: []
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
# WorldDisasterLM — Open Foundation Model for Global Disaster Intelligence
|
| 62 |
+
|
| 63 |
+
WorldDisasterLM is an instruction-tuned large language model built on top of
|
| 64 |
+
**Llama 3.1 8B Instruct**, domain-adapted on global disaster data from
|
| 65 |
+
ReliefWeb, USGS, NOAA, GDACS, OpenFEMA, and WHO.
|
| 66 |
+
|
| 67 |
+
## Model Details
|
| 68 |
+
|
| 69 |
+
| Property | Value |
|
| 70 |
+
|---|---|
|
| 71 |
+
| Base model | meta-llama/Llama-3.1-8B-Instruct |
|
| 72 |
+
| Training method | QLoRA (4-bit NF4 quantization, LoRA r=16) |
|
| 73 |
+
| Languages | EN, ES, FR, AR, HI, TE, ZH, JA, KO, PT |
|
| 74 |
+
| Domain | Disaster management, humanitarian response, risk intelligence |
|
| 75 |
+
| License | Llama 3 Community License (see Meta's terms) |
|
| 76 |
+
|
| 77 |
+
## Quick Start
|
| 78 |
+
|
| 79 |
+
```python
|
| 80 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 81 |
+
import torch
|
| 82 |
+
|
| 83 |
+
model_id = "YOUR_HF_USERNAME/WorldDisasterLM-8B"
|
| 84 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 85 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 86 |
+
model_id,
|
| 87 |
+
torch_dtype=torch.bfloat16,
|
| 88 |
+
device_map="auto",
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
messages = [
|
| 92 |
+
{
|
| 93 |
+
"role": "system",
|
| 94 |
+
"content": "You are WorldDisasterLM, an expert in disaster management and emergency response.",
|
| 95 |
+
},
|
| 96 |
+
{"role": "user", "content": "What should I do immediately after an earthquake?"},
|
| 97 |
+
]
|
| 98 |
+
inputs = tokenizer.apply_chat_template(messages, return_tensors="pt", add_generation_prompt=True)
|
| 99 |
+
outputs = model.generate(inputs.to(model.device), max_new_tokens=512, temperature=0.7)
|
| 100 |
+
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
## Training Data
|
| 104 |
+
|
| 105 |
+
Collected from free, publicly accessible sources:
|
| 106 |
+
- **ReliefWeb** — humanitarian reports and disaster assessments
|
| 107 |
+
- **USGS** — earthquake catalog (magnitude ≥4.0, 10-year archive)
|
| 108 |
+
- **NOAA** — weather alerts and severe weather events
|
| 109 |
+
- **GDACS** — global disaster alert coordination events
|
| 110 |
+
- **OpenFEMA** — US federal disaster declarations
|
| 111 |
+
- **WHO** — disease outbreak news and public health alerts
|
| 112 |
+
|
| 113 |
+
Each raw record was expanded into 8 instruction-following QA variants
|
| 114 |
+
(immediate response, resource planning, risk assessment, public communication,
|
| 115 |
+
recovery planning, multilingual guidance) for a multi-hundred-thousand sample corpus.
|
| 116 |
+
|
| 117 |
+
## Intended Use
|
| 118 |
+
|
| 119 |
+
- Emergency operations centers
|
| 120 |
+
- Government disaster management agencies
|
| 121 |
+
- NGOs and humanitarian organizations
|
| 122 |
+
- Public health authorities
|
| 123 |
+
- Researchers in disaster risk reduction
|
| 124 |
+
- Community preparedness applications
|
| 125 |
+
- Citizens seeking emergency guidance
|
| 126 |
+
|
| 127 |
+
## Safety and Limitations
|
| 128 |
+
|
| 129 |
+
- **Not a substitute** for real-time emergency management systems or official orders.
|
| 130 |
+
- Always verify critical operational decisions with local emergency authorities.
|
| 131 |
+
- Model outputs should be reviewed by trained emergency professionals for life-safety decisions.
|
| 132 |
+
- Some low-resource languages may have lower quality responses.
|
| 133 |
+
- Training data may not reflect the most recent real-time events.
|
| 134 |
+
|
| 135 |
+
## Citation
|
| 136 |
+
|
| 137 |
+
```bibtex
|
| 138 |
+
@misc{worlddisasterlm2026,
|
| 139 |
+
title = {WorldDisasterLM: An Open Foundation Model for Global Disaster Management},
|
| 140 |
+
year = {2026},
|
| 141 |
+
url = {https://huggingface.co/YOUR_HF_USERNAME/WorldDisasterLM-8B}
|
| 142 |
+
}
|
| 143 |
+
```
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def parse_args() -> argparse.Namespace:
|
| 148 |
+
parser = argparse.ArgumentParser(description="Merge LoRA adapters and push WorldDisasterLM to Hugging Face Hub")
|
| 149 |
+
parser.add_argument("--adapter", default="checkpoints/worlddisasterlm-qlora", help="Path to LoRA adapter checkpoint")
|
| 150 |
+
parser.add_argument("--base-model", default="meta-llama/Llama-3.1-8B-Instruct", help="Base model ID")
|
| 151 |
+
parser.add_argument("--repo-id", required=True, help="HF repo ID, e.g. YourUsername/WorldDisasterLM-8B")
|
| 152 |
+
parser.add_argument("--private", action="store_true", help="Create as private repo (default: public)")
|
| 153 |
+
parser.add_argument("--push-dtype", choices=["bfloat16", "float16", "float32"], default="bfloat16")
|
| 154 |
+
return parser.parse_args()
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
def merge_and_push(adapter_path: str, base_model_id: str, repo_id: str, private: bool, push_dtype: str) -> None:
|
| 158 |
+
import torch
|
| 159 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 160 |
+
from peft import PeftModel
|
| 161 |
+
from huggingface_hub import HfApi, create_repo
|
| 162 |
+
|
| 163 |
+
token = os.getenv("HF_TOKEN")
|
| 164 |
+
if not token:
|
| 165 |
+
raise SystemExit("HF_TOKEN environment variable not set. Run: huggingface-cli login")
|
| 166 |
+
|
| 167 |
+
dtype_map = {
|
| 168 |
+
"bfloat16": torch.bfloat16,
|
| 169 |
+
"float16": torch.float16,
|
| 170 |
+
"float32": torch.float32,
|
| 171 |
+
}
|
| 172 |
+
torch_dtype = dtype_map[push_dtype]
|
| 173 |
+
|
| 174 |
+
api = HfApi(token=token)
|
| 175 |
+
|
| 176 |
+
logger.info("Creating or verifying repo: %s", repo_id)
|
| 177 |
+
create_repo(repo_id=repo_id, token=token, private=private, repo_type="model", exist_ok=True)
|
| 178 |
+
|
| 179 |
+
logger.info("Loading tokenizer from adapter path: %s", adapter_path)
|
| 180 |
+
tokenizer = AutoTokenizer.from_pretrained(adapter_path, trust_remote_code=True)
|
| 181 |
+
|
| 182 |
+
logger.info("Loading base model: %s", base_model_id)
|
| 183 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 184 |
+
base_model_id,
|
| 185 |
+
torch_dtype=torch_dtype,
|
| 186 |
+
device_map="auto",
|
| 187 |
+
trust_remote_code=True,
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
logger.info("Loading LoRA adapter from: %s", adapter_path)
|
| 191 |
+
peft_model = PeftModel.from_pretrained(base_model, adapter_path)
|
| 192 |
+
|
| 193 |
+
logger.info("Merging LoRA weights into base model …")
|
| 194 |
+
merged_model = peft_model.merge_and_unload()
|
| 195 |
+
merged_model.config.use_cache = True
|
| 196 |
+
|
| 197 |
+
logger.info("Pushing merged model to %s …", repo_id)
|
| 198 |
+
merged_model.push_to_hub(repo_id, token=token, safe_serialization=True)
|
| 199 |
+
tokenizer.push_to_hub(repo_id, token=token)
|
| 200 |
+
|
| 201 |
+
# Upload model card
|
| 202 |
+
with tempfile.NamedTemporaryFile("w", suffix=".md", delete=False, encoding="utf-8") as tf:
|
| 203 |
+
tf.write(HF_MODEL_CARD.replace("YOUR_HF_USERNAME", repo_id.split("/")[0]))
|
| 204 |
+
tmp_card_path = tf.name
|
| 205 |
+
|
| 206 |
+
api.upload_file(
|
| 207 |
+
path_or_fileobj=tmp_card_path,
|
| 208 |
+
path_in_repo="README.md",
|
| 209 |
+
repo_id=repo_id,
|
| 210 |
+
repo_type="model",
|
| 211 |
+
token=token,
|
| 212 |
+
)
|
| 213 |
+
Path(tmp_card_path).unlink(missing_ok=True)
|
| 214 |
+
|
| 215 |
+
logger.info("Done! Model published at: https://huggingface.co/%s", repo_id)
|
| 216 |
+
logger.info("Tag your model as free-to-use by setting the license in the repo settings.")
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
def main() -> None:
|
| 220 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 221 |
+
args = parse_args()
|
| 222 |
+
merge_and_push(
|
| 223 |
+
adapter_path=args.adapter,
|
| 224 |
+
base_model_id=args.base_model,
|
| 225 |
+
repo_id=args.repo_id,
|
| 226 |
+
private=args.private,
|
| 227 |
+
push_dtype=args.push_dtype,
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
if __name__ == "__main__":
|
| 232 |
+
main()
|
scripts/run_pipeline.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import subprocess
|
| 3 |
+
import sys
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
STAGE_COMMANDS = {
|
| 7 |
+
"data": [sys.executable, "dataset_builder.py"],
|
| 8 |
+
"train": [sys.executable, "train.py"],
|
| 9 |
+
"evaluate": [sys.executable, "evaluate.py"],
|
| 10 |
+
"all": [
|
| 11 |
+
[sys.executable, "dataset_builder.py"],
|
| 12 |
+
[sys.executable, "train.py"],
|
| 13 |
+
[sys.executable, "evaluate.py"],
|
| 14 |
+
],
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def parse_args() -> argparse.Namespace:
|
| 19 |
+
parser = argparse.ArgumentParser(description="Run WorldDisasterLM pipeline stages")
|
| 20 |
+
parser.add_argument("--stage", choices=["data", "train", "evaluate", "all"], default="all")
|
| 21 |
+
return parser.parse_args()
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def run_command(command: list[str]) -> None:
|
| 25 |
+
print("Running:", " ".join(command))
|
| 26 |
+
subprocess.run(command, check=True)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main() -> None:
|
| 30 |
+
args = parse_args()
|
| 31 |
+
commands = STAGE_COMMANDS[args.stage]
|
| 32 |
+
|
| 33 |
+
if args.stage == "all":
|
| 34 |
+
for command in commands:
|
| 35 |
+
run_command(command)
|
| 36 |
+
else:
|
| 37 |
+
run_command(commands)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
if __name__ == "__main__":
|
| 41 |
+
main()
|
scripts/train_production.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Production training launcher with full CLI.
|
| 3 |
+
|
| 4 |
+
Usage
|
| 5 |
+
-----
|
| 6 |
+
# Minimal (uses all defaults)
|
| 7 |
+
python scripts/train_production.py
|
| 8 |
+
|
| 9 |
+
# Full options
|
| 10 |
+
python scripts/train_production.py \\
|
| 11 |
+
--dataset data/processed/instruction_dataset.jsonl \\
|
| 12 |
+
--base-model meta-llama/Llama-3.1-8B-Instruct \\
|
| 13 |
+
--output checkpoints/worlddisasterlm-qlora \\
|
| 14 |
+
--epochs 3 \\
|
| 15 |
+
--lora-r 16 \\
|
| 16 |
+
--batch-size 2 \\
|
| 17 |
+
--grad-accum 8 \\
|
| 18 |
+
--report-to wandb
|
| 19 |
+
|
| 20 |
+
# Consumer GPU (RTX 4090 24 GB)
|
| 21 |
+
python scripts/train_production.py \\
|
| 22 |
+
--batch-size 1 --grad-accum 16 --max-seq-length 1024
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
from __future__ import annotations
|
| 26 |
+
|
| 27 |
+
import argparse
|
| 28 |
+
import logging
|
| 29 |
+
|
| 30 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def parse_args() -> argparse.Namespace:
|
| 35 |
+
parser = argparse.ArgumentParser(description="Train WorldDisasterLM with QLoRA")
|
| 36 |
+
|
| 37 |
+
# Model / data
|
| 38 |
+
parser.add_argument("--base-model", default="meta-llama/Llama-3.1-8B-Instruct")
|
| 39 |
+
parser.add_argument("--dataset", default="data/processed/instruction_dataset.jsonl")
|
| 40 |
+
parser.add_argument("--output", default="checkpoints/worlddisasterlm-qlora")
|
| 41 |
+
parser.add_argument("--max-seq-length", type=int, default=2048)
|
| 42 |
+
|
| 43 |
+
# Training
|
| 44 |
+
parser.add_argument("--epochs", type=int, default=3)
|
| 45 |
+
parser.add_argument("--learning-rate", type=float, default=2e-4)
|
| 46 |
+
parser.add_argument("--batch-size", type=int, default=2)
|
| 47 |
+
parser.add_argument("--grad-accum", type=int, default=8)
|
| 48 |
+
parser.add_argument("--warmup-ratio", type=float, default=0.03)
|
| 49 |
+
|
| 50 |
+
# LoRA
|
| 51 |
+
parser.add_argument("--lora-r", type=int, default=16)
|
| 52 |
+
parser.add_argument("--lora-alpha", type=int, default=32)
|
| 53 |
+
parser.add_argument("--lora-dropout", type=float, default=0.05)
|
| 54 |
+
|
| 55 |
+
# Hardware
|
| 56 |
+
parser.add_argument("--no-4bit", action="store_true", help="Disable 4-bit quantization (needs more VRAM)")
|
| 57 |
+
parser.add_argument("--fp16", action="store_true", help="Use fp16 instead of bf16")
|
| 58 |
+
|
| 59 |
+
# Tracking
|
| 60 |
+
parser.add_argument("--report-to", choices=["mlflow", "wandb", "none"], default="none")
|
| 61 |
+
parser.add_argument("--seed", type=int, default=42)
|
| 62 |
+
|
| 63 |
+
return parser.parse_args()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main() -> None:
|
| 67 |
+
args = parse_args()
|
| 68 |
+
|
| 69 |
+
from worlddisasterlm.training.train_qlora import QLoRAConfig, train
|
| 70 |
+
|
| 71 |
+
config = QLoRAConfig(
|
| 72 |
+
base_model=args.base_model,
|
| 73 |
+
output_dir=args.output,
|
| 74 |
+
dataset_path=args.dataset,
|
| 75 |
+
max_seq_length=args.max_seq_length,
|
| 76 |
+
use_4bit=not args.no_4bit,
|
| 77 |
+
epochs=args.epochs,
|
| 78 |
+
learning_rate=args.learning_rate,
|
| 79 |
+
per_device_train_batch_size=args.batch_size,
|
| 80 |
+
gradient_accumulation_steps=args.grad_accum,
|
| 81 |
+
warmup_ratio=args.warmup_ratio,
|
| 82 |
+
lora_r=args.lora_r,
|
| 83 |
+
lora_alpha=args.lora_alpha,
|
| 84 |
+
lora_dropout=args.lora_dropout,
|
| 85 |
+
bf16=not args.fp16,
|
| 86 |
+
fp16=args.fp16,
|
| 87 |
+
report_to=args.report_to,
|
| 88 |
+
seed=args.seed,
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
logger.info("Effective training config: %s", config)
|
| 92 |
+
train(config)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
if __name__ == "__main__":
|
| 96 |
+
main()
|
scripts/upload_space.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Upload WorldDisasterLM-8B as a public HuggingFace Space (Gradio demo).
|
| 3 |
+
|
| 4 |
+
Usage
|
| 5 |
+
-----
|
| 6 |
+
# Set your HF token first:
|
| 7 |
+
$env:HF_TOKEN = "hf_xxxxxxxxxxxxxxxxxxxx"
|
| 8 |
+
|
| 9 |
+
# Then run:
|
| 10 |
+
python scripts/upload_space.py --username YOUR_HF_USERNAME
|
| 11 |
+
|
| 12 |
+
# Optionally specify a custom space name:
|
| 13 |
+
python scripts/upload_space.py --username YOUR_HF_USERNAME --space-name WorldDisasterLM-8B
|
| 14 |
+
|
| 15 |
+
Requirements
|
| 16 |
+
------------
|
| 17 |
+
pip install huggingface_hub
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import os
|
| 24 |
+
import sys
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
ROOT = Path(__file__).parent.parent
|
| 28 |
+
SPACE_DIR = ROOT / "hf_space"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def parse_args() -> argparse.Namespace:
|
| 32 |
+
p = argparse.ArgumentParser(description="Upload WorldDisasterLM-8B to HuggingFace Spaces")
|
| 33 |
+
p.add_argument("--username", required=True, help="Your HuggingFace username")
|
| 34 |
+
p.add_argument("--space-name", default="WorldDisasterLM-8B", help="Space repository name")
|
| 35 |
+
p.add_argument("--private", action="store_true", help="Create as private space (default: public)")
|
| 36 |
+
return p.parse_args()
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def main() -> None:
|
| 40 |
+
args = parse_args()
|
| 41 |
+
|
| 42 |
+
# Check token
|
| 43 |
+
token = os.environ.get("HF_TOKEN")
|
| 44 |
+
if not token:
|
| 45 |
+
print("ERROR: HF_TOKEN environment variable is not set.")
|
| 46 |
+
print(" Set it with: $env:HF_TOKEN = 'hf_xxxxxxxxxxxx'")
|
| 47 |
+
sys.exit(1)
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
from huggingface_hub import HfApi, create_repo
|
| 51 |
+
except ImportError:
|
| 52 |
+
print("ERROR: huggingface_hub not installed. Run: pip install huggingface_hub")
|
| 53 |
+
sys.exit(1)
|
| 54 |
+
|
| 55 |
+
repo_id = f"{args.username}/{args.space_name}"
|
| 56 |
+
api = HfApi(token=token)
|
| 57 |
+
|
| 58 |
+
print(f"\n{'='*60}")
|
| 59 |
+
print(f" WorldDisasterLM-8B → HuggingFace Space")
|
| 60 |
+
print(f" Repo : {repo_id}")
|
| 61 |
+
print(f" Private: {args.private}")
|
| 62 |
+
print(f"{'='*60}\n")
|
| 63 |
+
|
| 64 |
+
# 1. Create the Space repo
|
| 65 |
+
print("Step 1/3 — Creating Space repository...")
|
| 66 |
+
create_repo(
|
| 67 |
+
repo_id=repo_id,
|
| 68 |
+
repo_type="space",
|
| 69 |
+
space_sdk="gradio",
|
| 70 |
+
private=args.private,
|
| 71 |
+
exist_ok=True,
|
| 72 |
+
token=token,
|
| 73 |
+
)
|
| 74 |
+
print(f" ✓ Space created: https://huggingface.co/spaces/{repo_id}")
|
| 75 |
+
|
| 76 |
+
# 2. Patch README.md with actual username
|
| 77 |
+
readme_src = SPACE_DIR / "README.md"
|
| 78 |
+
readme_text = readme_src.read_text(encoding="utf-8")
|
| 79 |
+
readme_text = readme_text.replace("YOUR_HF_USERNAME", args.username)
|
| 80 |
+
|
| 81 |
+
import tempfile, shutil
|
| 82 |
+
tmp_dir = Path(tempfile.mkdtemp())
|
| 83 |
+
try:
|
| 84 |
+
# Copy space files to temp dir with patched README
|
| 85 |
+
shutil.copytree(str(SPACE_DIR), str(tmp_dir / "space"))
|
| 86 |
+
(tmp_dir / "space" / "README.md").write_text(readme_text, encoding="utf-8")
|
| 87 |
+
|
| 88 |
+
# 3. Upload the folder
|
| 89 |
+
print("Step 2/3 — Uploading files...")
|
| 90 |
+
api.upload_folder(
|
| 91 |
+
folder_path=str(tmp_dir / "space"),
|
| 92 |
+
repo_id=repo_id,
|
| 93 |
+
repo_type="space",
|
| 94 |
+
commit_message="Upload WorldDisasterLM-8B Space demo",
|
| 95 |
+
token=token,
|
| 96 |
+
)
|
| 97 |
+
print(" ✓ Files uploaded")
|
| 98 |
+
finally:
|
| 99 |
+
shutil.rmtree(tmp_dir, ignore_errors=True)
|
| 100 |
+
|
| 101 |
+
print("\nStep 3/3 — Verifying Space...")
|
| 102 |
+
space_info = api.space_info(repo_id=repo_id, token=token)
|
| 103 |
+
print(f" ✓ Space status: {getattr(space_info, 'runtime', {})}")
|
| 104 |
+
|
| 105 |
+
print(f"\n{'='*60}")
|
| 106 |
+
print(f" DONE! Your Space is live at:")
|
| 107 |
+
print(f" https://huggingface.co/spaces/{repo_id}")
|
| 108 |
+
print(f"{'='*60}\n")
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
main()
|
scripts/upload_space_urllib.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Upload WorldDisasterLM-8B as a public HuggingFace Space using urllib (no httpx/requests).
|
| 3 |
+
Works on Python 3.14 on Windows where httpx TLS may fail.
|
| 4 |
+
|
| 5 |
+
Usage
|
| 6 |
+
-----
|
| 7 |
+
python scripts/upload_space_urllib.py --token hf_xxx --username drdeveloper88
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import base64
|
| 14 |
+
import json
|
| 15 |
+
import os
|
| 16 |
+
import shutil
|
| 17 |
+
import ssl
|
| 18 |
+
import sys
|
| 19 |
+
import tempfile
|
| 20 |
+
import urllib.request
|
| 21 |
+
from pathlib import Path
|
| 22 |
+
|
| 23 |
+
ROOT = Path(__file__).parent.parent
|
| 24 |
+
SPACE_DIR = ROOT / "hf_space"
|
| 25 |
+
HF_API = "https://huggingface.co/api"
|
| 26 |
+
|
| 27 |
+
CTX = ssl.create_default_context()
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def api(method: str, path: str, token: str, payload: dict | None = None) -> dict:
|
| 31 |
+
url = f"{HF_API}{path}"
|
| 32 |
+
data = json.dumps(payload).encode() if payload else None
|
| 33 |
+
req = urllib.request.Request(
|
| 34 |
+
url,
|
| 35 |
+
data=data,
|
| 36 |
+
method=method,
|
| 37 |
+
headers={
|
| 38 |
+
"Authorization": f"Bearer {token}",
|
| 39 |
+
"Content-Type": "application/json",
|
| 40 |
+
},
|
| 41 |
+
)
|
| 42 |
+
try:
|
| 43 |
+
with urllib.request.urlopen(req, context=CTX, timeout=30) as r:
|
| 44 |
+
return json.loads(r.read())
|
| 45 |
+
except urllib.error.HTTPError as e:
|
| 46 |
+
body = e.read().decode()
|
| 47 |
+
raise RuntimeError(f"HTTP {e.code}: {body}") from e
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def upload_file(repo_id: str, token: str, local_path: Path, repo_path: str, commit_msg: str) -> None:
|
| 51 |
+
"""Upload a single file via the HF /api/repos endpoint (LFS-aware preupload + commit)."""
|
| 52 |
+
content = local_path.read_bytes()
|
| 53 |
+
|
| 54 |
+
# Step 1: preupload (get upload URL or confirm non-LFS)
|
| 55 |
+
preupload_url = f"https://huggingface.co/api/spaces/{repo_id}/preupload/main"
|
| 56 |
+
payload = json.dumps([{"path": repo_path, "size": len(content)}]).encode()
|
| 57 |
+
req = urllib.request.Request(
|
| 58 |
+
preupload_url,
|
| 59 |
+
data=payload,
|
| 60 |
+
method="POST",
|
| 61 |
+
headers={
|
| 62 |
+
"Authorization": f"Bearer {token}",
|
| 63 |
+
"Content-Type": "application/json",
|
| 64 |
+
},
|
| 65 |
+
)
|
| 66 |
+
with urllib.request.urlopen(req, context=CTX, timeout=30) as r:
|
| 67 |
+
preupload = json.loads(r.read())
|
| 68 |
+
|
| 69 |
+
files_info = preupload.get("files", [{}])
|
| 70 |
+
upload_mode = files_info[0].get("uploadMode", "regular") if files_info else "regular"
|
| 71 |
+
|
| 72 |
+
if upload_mode == "lfs":
|
| 73 |
+
# Upload to LFS URL
|
| 74 |
+
upload_url = files_info[0]["uploadUrl"]
|
| 75 |
+
put_req = urllib.request.Request(
|
| 76 |
+
upload_url,
|
| 77 |
+
data=content,
|
| 78 |
+
method="PUT",
|
| 79 |
+
headers={"Content-Type": "application/octet-stream"},
|
| 80 |
+
)
|
| 81 |
+
with urllib.request.urlopen(put_req, context=CTX, timeout=60) as r:
|
| 82 |
+
r.read()
|
| 83 |
+
oid = files_info[0]["oid"]
|
| 84 |
+
size = files_info[0]["size"]
|
| 85 |
+
lfs_content = (
|
| 86 |
+
f"version https://git-lfs.github.com/spec/v1\n"
|
| 87 |
+
f"oid sha256:{oid}\n"
|
| 88 |
+
f"size {size}\n"
|
| 89 |
+
).encode()
|
| 90 |
+
final_content = lfs_content
|
| 91 |
+
else:
|
| 92 |
+
final_content = content
|
| 93 |
+
|
| 94 |
+
# Step 2: commit
|
| 95 |
+
commit_url = f"https://huggingface.co/api/spaces/{repo_id}/commit/main"
|
| 96 |
+
header = json.dumps({
|
| 97 |
+
"summary": commit_msg,
|
| 98 |
+
"files": [{"path": repo_path, "encoding": "base64"}],
|
| 99 |
+
})
|
| 100 |
+
boundary = "----HFUploadBoundary"
|
| 101 |
+
body_parts = [
|
| 102 |
+
f"--{boundary}\r\nContent-Disposition: form-data; name=\"header\"\r\n\r\n{header}\r\n".encode(),
|
| 103 |
+
f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{repo_path}\"\r\n\r\n".encode(),
|
| 104 |
+
base64.b64encode(final_content),
|
| 105 |
+
f"\r\n--{boundary}--\r\n".encode(),
|
| 106 |
+
]
|
| 107 |
+
body = b"".join(body_parts)
|
| 108 |
+
commit_req = urllib.request.Request(
|
| 109 |
+
commit_url,
|
| 110 |
+
data=body,
|
| 111 |
+
method="POST",
|
| 112 |
+
headers={
|
| 113 |
+
"Authorization": f"Bearer {token}",
|
| 114 |
+
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
| 115 |
+
},
|
| 116 |
+
)
|
| 117 |
+
with urllib.request.urlopen(commit_req, context=CTX, timeout=60) as r:
|
| 118 |
+
r.read()
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def upload_folder_simple(repo_id: str, token: str, folder: Path, readme_text: str) -> None:
|
| 122 |
+
"""Upload all Space files using the simpler single-commit API."""
|
| 123 |
+
files_payload = []
|
| 124 |
+
file_contents = {}
|
| 125 |
+
|
| 126 |
+
for f in folder.iterdir():
|
| 127 |
+
if f.is_file():
|
| 128 |
+
if f.name == "README.md":
|
| 129 |
+
content = readme_text.encode("utf-8")
|
| 130 |
+
else:
|
| 131 |
+
content = f.read_bytes()
|
| 132 |
+
encoded = base64.b64encode(content).decode()
|
| 133 |
+
files_payload.append({"path": f.name, "encoding": "base64"})
|
| 134 |
+
file_contents[f.name] = encoded
|
| 135 |
+
|
| 136 |
+
# Build multipart commit
|
| 137 |
+
boundary = "HFSpaceUpload42"
|
| 138 |
+
parts = []
|
| 139 |
+
header = json.dumps({
|
| 140 |
+
"summary": "Upload WorldDisasterLM-8B Space",
|
| 141 |
+
"files": files_payload,
|
| 142 |
+
})
|
| 143 |
+
parts.append(
|
| 144 |
+
f"--{boundary}\r\nContent-Disposition: form-data; name=\"header\"\r\n\r\n{header}\r\n".encode()
|
| 145 |
+
)
|
| 146 |
+
for item in files_payload:
|
| 147 |
+
fname = item["path"]
|
| 148 |
+
parts.append(
|
| 149 |
+
f"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"{fname}\"\r\n\r\n".encode()
|
| 150 |
+
)
|
| 151 |
+
parts.append(file_contents[fname].encode())
|
| 152 |
+
parts.append(b"\r\n")
|
| 153 |
+
parts.append(f"--{boundary}--\r\n".encode())
|
| 154 |
+
body = b"".join(parts)
|
| 155 |
+
|
| 156 |
+
commit_url = f"https://huggingface.co/api/spaces/{repo_id}/commit/main"
|
| 157 |
+
req = urllib.request.Request(
|
| 158 |
+
commit_url,
|
| 159 |
+
data=body,
|
| 160 |
+
method="POST",
|
| 161 |
+
headers={
|
| 162 |
+
"Authorization": f"Bearer {token}",
|
| 163 |
+
"Content-Type": f"multipart/form-data; boundary={boundary}",
|
| 164 |
+
},
|
| 165 |
+
)
|
| 166 |
+
try:
|
| 167 |
+
with urllib.request.urlopen(req, context=CTX, timeout=120) as r:
|
| 168 |
+
result = json.loads(r.read())
|
| 169 |
+
return result
|
| 170 |
+
except urllib.error.HTTPError as e:
|
| 171 |
+
raise RuntimeError(f"Commit failed HTTP {e.code}: {e.read().decode()}") from e
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def parse_args() -> argparse.Namespace:
|
| 175 |
+
p = argparse.ArgumentParser()
|
| 176 |
+
p.add_argument("--token", default=os.environ.get("HF_TOKEN"), help="HF token (or set HF_TOKEN env var)")
|
| 177 |
+
p.add_argument("--username", required=True)
|
| 178 |
+
p.add_argument("--space-name", default="WorldDisasterLM-8B")
|
| 179 |
+
p.add_argument("--private", action="store_true")
|
| 180 |
+
return p.parse_args()
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
def main() -> None:
|
| 184 |
+
args = parse_args()
|
| 185 |
+
token = args.token
|
| 186 |
+
if not token:
|
| 187 |
+
print("ERROR: provide --token or set HF_TOKEN"); sys.exit(1)
|
| 188 |
+
|
| 189 |
+
repo_id = f"{args.username}/{args.space_name}"
|
| 190 |
+
|
| 191 |
+
print(f"\n{'='*60}")
|
| 192 |
+
print(f" WorldDisasterLM-8B → HuggingFace Space")
|
| 193 |
+
print(f" Repo: {repo_id}")
|
| 194 |
+
print(f"{'='*60}\n")
|
| 195 |
+
|
| 196 |
+
# Verify token
|
| 197 |
+
user = api("GET", "/whoami-v2", token)
|
| 198 |
+
print(f" Authenticated as: {user.get('name')}")
|
| 199 |
+
|
| 200 |
+
# Step 1: Create Space
|
| 201 |
+
print("\nStep 1/3 — Creating Space repository...")
|
| 202 |
+
try:
|
| 203 |
+
api("POST", "/repos/create", token, {
|
| 204 |
+
"type": "space",
|
| 205 |
+
"name": args.space_name,
|
| 206 |
+
"sdk": "gradio",
|
| 207 |
+
"private": args.private,
|
| 208 |
+
"exist_ok": True,
|
| 209 |
+
})
|
| 210 |
+
print(f" ✓ Space created")
|
| 211 |
+
except RuntimeError as e:
|
| 212 |
+
if "already exists" in str(e) or "409" in str(e):
|
| 213 |
+
print(f" ✓ Space already exists, updating")
|
| 214 |
+
else:
|
| 215 |
+
raise
|
| 216 |
+
|
| 217 |
+
# Step 2: Patch README
|
| 218 |
+
readme_text = (SPACE_DIR / "README.md").read_text(encoding="utf-8")
|
| 219 |
+
readme_text = readme_text.replace("YOUR_HF_USERNAME", args.username)
|
| 220 |
+
|
| 221 |
+
# Step 3: Upload files
|
| 222 |
+
print("Step 2/3 — Uploading files...")
|
| 223 |
+
result = upload_folder_simple(repo_id, token, SPACE_DIR, readme_text)
|
| 224 |
+
print(f" ✓ Files uploaded (commit: {result.get('commitOid', 'ok')[:12] if result.get('commitOid') else 'done'})")
|
| 225 |
+
|
| 226 |
+
print(f"\nStep 3/3 — Done!\n")
|
| 227 |
+
print(f"{'='*60}")
|
| 228 |
+
print(f" Live at: https://huggingface.co/spaces/{repo_id}")
|
| 229 |
+
print(f" (Space may take 1-2 min to build)")
|
| 230 |
+
print(f"{'='*60}\n")
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
if __name__ == "__main__":
|
| 234 |
+
main()
|
tests/test_api.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from fastapi.testclient import TestClient
|
| 2 |
+
|
| 3 |
+
from backend.app.main import app
|
| 4 |
+
|
| 5 |
+
client = TestClient(app)
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def test_health() -> None:
|
| 9 |
+
response = client.get("/health")
|
| 10 |
+
assert response.status_code == 200
|
| 11 |
+
payload = response.json()
|
| 12 |
+
assert payload["status"] == "ok"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_chat_endpoint() -> None:
|
| 16 |
+
response = client.post(
|
| 17 |
+
"/v1/chat",
|
| 18 |
+
json={
|
| 19 |
+
"messages": [{"role": "user", "content": "What is the first step during flood evacuation?"}],
|
| 20 |
+
"language": "English",
|
| 21 |
+
"region": "global",
|
| 22 |
+
},
|
| 23 |
+
)
|
| 24 |
+
assert response.status_code == 200
|
| 25 |
+
assert "answer" in response.json()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def test_chat_nepali_language() -> None:
|
| 29 |
+
"""Verify Nepali language requests return a Nepali-language answer."""
|
| 30 |
+
response = client.post(
|
| 31 |
+
"/v1/chat",
|
| 32 |
+
json={
|
| 33 |
+
"messages": [{"role": "user", "content": "भूकम्पको बेला के गर्ने?"}],
|
| 34 |
+
"language": "Nepali",
|
| 35 |
+
"region": "Nepal",
|
| 36 |
+
},
|
| 37 |
+
)
|
| 38 |
+
assert response.status_code == 200
|
| 39 |
+
data = response.json()
|
| 40 |
+
assert "answer" in data
|
| 41 |
+
# Response should contain Devanagari characters for Nepali output
|
| 42 |
+
assert any("\u0900" <= ch <= "\u097F" for ch in data["answer"]), (
|
| 43 |
+
"Expected Devanagari script in Nepali response"
|
| 44 |
+
)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def test_chat_nepali_confidence_range() -> None:
|
| 48 |
+
"""Nepali response confidence must be in valid [0, 1] range."""
|
| 49 |
+
response = client.post(
|
| 50 |
+
"/v1/chat",
|
| 51 |
+
json={
|
| 52 |
+
"messages": [{"role": "user", "content": "बाढीको बेला के गर्ने?"}],
|
| 53 |
+
"language": "Nepali",
|
| 54 |
+
"region": "Nepal",
|
| 55 |
+
},
|
| 56 |
+
)
|
| 57 |
+
assert response.status_code == 200
|
| 58 |
+
data = response.json()
|
| 59 |
+
assert 0.0 <= data["confidence"] <= 1.0
|
tests/test_dataset_builder.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from worlddisasterlm.config import SUPPORTED_LANGUAGES
|
| 2 |
+
from worlddisasterlm.data.etl import DisasterETL
|
| 3 |
+
from worlddisasterlm.data.processors import build_instruction_dataset
|
| 4 |
+
from worlddisasterlm.data.scenario_builder import build_all_scenarios
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def test_dataset_builder_generates_samples() -> None:
|
| 8 |
+
etl = DisasterETL()
|
| 9 |
+
records = etl.normalize(etl.deduplicate(etl.collect_records()))
|
| 10 |
+
samples = build_instruction_dataset(records)
|
| 11 |
+
assert len(samples) > 0
|
| 12 |
+
assert samples[0].instruction
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def test_nepali_in_supported_languages() -> None:
|
| 16 |
+
"""Nepali must be present in SUPPORTED_LANGUAGES."""
|
| 17 |
+
assert "Nepali" in SUPPORTED_LANGUAGES
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def test_nepali_scenario_samples_exist() -> None:
|
| 21 |
+
"""At least one Nepali-language training sample must be built from scenarios."""
|
| 22 |
+
samples = build_all_scenarios()
|
| 23 |
+
nepali_samples = [s for s in samples if s.language.lower() == "nepali"]
|
| 24 |
+
assert len(nepali_samples) >= 3, (
|
| 25 |
+
f"Expected >=3 Nepali samples, found {len(nepali_samples)}"
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def test_nepali_samples_use_devanagari() -> None:
|
| 30 |
+
"""Nepali scenario instructions must contain Devanagari Unicode characters."""
|
| 31 |
+
samples = build_all_scenarios()
|
| 32 |
+
nepali_samples = [s for s in samples if s.language.lower() == "nepali"]
|
| 33 |
+
for sample in nepali_samples:
|
| 34 |
+
assert any("\u0900" <= ch <= "\u097F" for ch in sample.instruction), (
|
| 35 |
+
f"Nepali sample missing Devanagari: {sample.instruction!r}"
|
| 36 |
+
)
|
tests/test_risk_engine.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from backend.app.services.risk_engine import compute_risk
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def test_compute_risk_range() -> None:
|
| 5 |
+
response = compute_risk("flood", vulnerability_index=0.7, exposure_index=0.8)
|
| 6 |
+
assert 0.0 <= response.risk_score <= 1.0
|
| 7 |
+
assert response.risk_level in {"low", "moderate", "high", "critical"}
|
train.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""train.py — top-level entry-point, delegates to QLoRA production pipeline.
|
| 2 |
+
|
| 3 |
+
For full CLI options use:
|
| 4 |
+
python scripts/train_production.py --help
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import logging
|
| 11 |
+
|
| 12 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def parse_args() -> argparse.Namespace:
|
| 17 |
+
parser = argparse.ArgumentParser(description="Train WorldDisasterLM-8B (QLoRA)")
|
| 18 |
+
parser.add_argument("--base-model", default="meta-llama/Llama-3.1-8B-Instruct")
|
| 19 |
+
parser.add_argument("--dataset", default="data/processed/instruction_dataset.jsonl")
|
| 20 |
+
parser.add_argument("--output", default="checkpoints/worlddisasterlm-qlora")
|
| 21 |
+
parser.add_argument("--epochs", type=int, default=3)
|
| 22 |
+
parser.add_argument("--learning-rate", type=float, default=2e-4)
|
| 23 |
+
parser.add_argument("--batch-size", type=int, default=2)
|
| 24 |
+
parser.add_argument("--grad-accum", type=int, default=8)
|
| 25 |
+
parser.add_argument("--lora-r", type=int, default=16)
|
| 26 |
+
parser.add_argument("--report-to", choices=["mlflow", "wandb", "none"], default="none")
|
| 27 |
+
return parser.parse_args()
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def main() -> None:
|
| 31 |
+
args = parse_args()
|
| 32 |
+
|
| 33 |
+
try:
|
| 34 |
+
from worlddisasterlm.training.train_qlora import QLoRAConfig, train
|
| 35 |
+
except ImportError:
|
| 36 |
+
# Graceful fallback if GPU stack (torch/bitsandbytes) not installed
|
| 37 |
+
logger.warning(
|
| 38 |
+
"QLoRA dependencies not available. Using lightweight stub training. "
|
| 39 |
+
"Install with: pip install torch bitsandbytes peft trl"
|
| 40 |
+
)
|
| 41 |
+
from worlddisasterlm.training.fine_tune import TrainingConfig, run_training # type: ignore[import]
|
| 42 |
+
run_training(TrainingConfig(
|
| 43 |
+
base_model=args.base_model,
|
| 44 |
+
dataset_path=args.dataset,
|
| 45 |
+
output_dir=args.output,
|
| 46 |
+
epochs=args.epochs,
|
| 47 |
+
learning_rate=args.learning_rate,
|
| 48 |
+
batch_size=args.batch_size,
|
| 49 |
+
))
|
| 50 |
+
return
|
| 51 |
+
|
| 52 |
+
config = QLoRAConfig(
|
| 53 |
+
base_model=args.base_model,
|
| 54 |
+
dataset_path=args.dataset,
|
| 55 |
+
output_dir=args.output,
|
| 56 |
+
epochs=args.epochs,
|
| 57 |
+
learning_rate=args.learning_rate,
|
| 58 |
+
per_device_train_batch_size=args.batch_size,
|
| 59 |
+
gradient_accumulation_steps=args.grad_accum,
|
| 60 |
+
lora_r=args.lora_r,
|
| 61 |
+
report_to=args.report_to,
|
| 62 |
+
)
|
| 63 |
+
train(config)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
if __name__ == "__main__":
|
| 67 |
+
main()
|