Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: CI

on:
push:
branches: [main, refactor/4.9.0]
pull_request:

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
# Error-level rules only for now (syntax errors, undefined names,
# invalid prints/f-strings). Widen as the Phase 2 burndown lands.
- run: uvx ruff check --select E9,F63,F7,F82 gas neurons tests

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- name: Install locked dependencies (minus the GPU stack)
# Single source of truth: every version comes from uv.lock.
# The GPU stack (several GB of CUDA wheels) is skipped by NAME
# only — the current tests don't import torch. If a test ever
# needs it, drop the corresponding flag.
run: >
uv sync --frozen
--no-install-package torch
--no-install-package torchvision
--no-install-package torchaudio
--no-install-package torchcodec
--no-install-package triton
--no-install-package bitsandbytes
--no-install-package open-clip-torch
--no-install-package nvidia-cublas-cu12
--no-install-package nvidia-cuda-cupti-cu12
--no-install-package nvidia-cuda-nvrtc-cu12
--no-install-package nvidia-cuda-runtime-cu12
--no-install-package nvidia-cudnn-cu12
--no-install-package nvidia-cufft-cu12
--no-install-package nvidia-cufile-cu12
--no-install-package nvidia-curand-cu12
--no-install-package nvidia-cusolver-cu12
--no-install-package nvidia-cusparse-cu12
--no-install-package nvidia-cusparselt-cu12
--no-install-package nvidia-nccl-cu12
--no-install-package nvidia-nvjitlink-cu12
--no-install-package nvidia-nvtx-cu12
- name: Run tests
run: uv run --no-sync pytest tests/ -q
48 changes: 19 additions & 29 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ __pycache__/
*.py[cod]
*$py.class
*.so

# Node.js (not used - Python only project)
node_modules/
package.json
package-lock.json
.Python
build/
develop-eggs/
dist/
Expand All @@ -27,17 +21,26 @@ wheels/
.coverage
*.ipynb

# Node.js
node_modules/
package.json
package-lock.json

# Virtual environments
venv/
env/
ENV/
.venv/
.Python

# IDE
# IDE / editors
.vscode/
.idea/
*.swp
*.swo
\#*\#
.#*
*~

# OS
.DS_Store
Expand All @@ -46,39 +49,26 @@ Thumbs.db
# Logs
*.log

# Environment variables
# Environment variables (catch-all; templates stay tracked)
.env
.env.local
.env.development
.env.test
.env.production
.env.*
*.env
!*.env.template
!.env.*.template

# Model files and cache
*.pth
*.pt
*.ckpt
*.onnx
.cache/
downloaded_models/*

# Bittensor specific
~/.bittensor/
*.wallet

.env.validator
.env.miner
.env.generator
.env.mock_miner

*.onnx
downloaded_models/*
*.wallet

.env.gen_miner
base_miner/test_output/*
base_miner/.env.gen_miner
*.onnx
downloaded_models/*

generated_content/*

wandb/*

sample-images/
sample-images/
23 changes: 0 additions & 23 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -71,32 +71,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
python3.10-dev \
build-essential \
ffmpeg \
xvfb \
wget \
gnupg2 \
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libxcomposite1 \
libxdamage1 \
libxrandr2 \
libgbm1 \
libpango-1.0-0 \
libcairo2 \
libasound2 \
libxshmfence1 \
&& rm -rf /var/lib/apt/lists/*

# Install Google Chrome (needed by data service Selenium scraper)
RUN wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | apt-key add - && \
echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" \
> /etc/apt/sources.list.d/google-chrome.list && \
apt-get update && \
apt-get install -y --no-install-recommends google-chrome-stable && \
rm -rf /var/lib/apt/lists/*

# Make python3.10 the default
RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.10 1 && \
update-alternatives --install /usr/bin/python python /usr/bin/python3.10 1
Expand Down
4 changes: 1 addition & 3 deletions docs/Installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ The installation script will automatically install the following system dependen

- **Build tools**: pkg-config, cmake
- **Media processing**: ffmpeg
- **Browser automation**: Google Chrome, libnss3, libnspr4, xvfb
- **Process management**: Node.js, npm, PM2, dotenv

**Note**: The `--no-system-deps` option is primarily intended for **discriminative miners** who only need to submit models and don't require Chrome browser automation or Node.js process management tools. Validators should use the full installation to ensure all dependencies are available.
**Note**: The `--no-system-deps` option is primarily intended for **discriminative miners** who only need to submit models and don't require Node.js process management tools. Validators should use the full installation to ensure all dependencies are available.

### Installing uv

Expand Down Expand Up @@ -49,7 +48,6 @@ The installation script will:
- Check for Python 3.10+ and uv
- Install system dependencies (unless `--no-system-deps` is specified):
- pkg-config, cmake, ffmpeg
- chrome web driver, libnss3, libnspr4, xvfb
- Node.js, npm, PM2, dotenv
- Create a virtual environment using uv (fast dependency resolution)
- Install all dependencies from `pyproject.toml`
Expand Down
17 changes: 10 additions & 7 deletions gas/cache/db/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def __init__(self, db_path: Path):
@contextlib.contextmanager
def connect(self, max_retries: int = 3, retry_delay: float = 1.0) -> Iterator[sqlite3.Connection]:
"""Context manager for database connections with retry logic."""
conn = None
for attempt in range(max_retries):
try:
conn = sqlite3.connect(
Expand All @@ -30,11 +31,11 @@ def connect(self, max_retries: int = 3, retry_delay: float = 1.0) -> Iterator[sq
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA busy_timeout=30000")
conn.execute("PRAGMA foreign_keys=ON")

yield conn
conn.close()
return
break
except sqlite3.OperationalError as e:
if conn is not None:
conn.close()
conn = None
if "unable to open database file" in str(e) or "database is locked" in str(e):
if attempt < max_retries - 1:
bt.logging.warning(
Expand All @@ -46,9 +47,11 @@ def connect(self, max_retries: int = 3, retry_delay: float = 1.0) -> Iterator[sq
bt.logging.error(f"Failed to open database after {max_retries} attempts: {e}")
raise
raise
except Exception as e:
bt.logging.error(f"Database error: {e}")
raise

try:
yield conn
finally:
conn.close()


def create_schema(conn: sqlite3.Connection) -> None:
Expand Down
13 changes: 8 additions & 5 deletions gas/cache/db/media_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,23 +360,27 @@ def get_unuploaded_media(
conn.row_factory = __import__("sqlite3").Row
base_where = "(uploaded = 0 OR uploaded IS NULL)"

params: list = []
if source_type == 'miner':
source_filter = "(source_type = 'miner' AND verified = 1)"
elif source_type == 'generated':
source_filter = "source_type = 'generated'"
elif source_type in ('scraper', 'dataset'):
source_filter = f"source_type = '{source_type}'"
source_filter = "source_type = ?"
params.append(source_type)
else:
source_filter = "(source_type IN ('scraper', 'dataset', 'generated') OR (source_type = 'miner' AND verified = 1))"

query = f"SELECT * FROM media WHERE {base_where} AND {source_filter}"
if modality:
query += f" AND modality = '{modality}'"
query += " AND modality = ?"
params.append(modality)
query += " ORDER BY (source_type = 'miner' AND verified = 1) DESC, created_at ASC"
if limit is not None:
query += f" LIMIT {limit}"
query += " LIMIT ?"
params.append(limit)

cursor = conn.execute(query)
cursor = conn.execute(query, params)
entries = []
for row in cursor.fetchall():
entry = self._row_to_media_entry(row)
Expand Down Expand Up @@ -717,7 +721,6 @@ def cleanup_uploaded_media(
prompt_ids = [r["prompt_id"] for r in rows if r["prompt_id"]]

mid_ph = ",".join("?" * len(media_ids))
conn.execute(f"DELETE FROM prompts WHERE id IN ({mid_ph})", media_ids)
conn.execute(f"DELETE FROM generator_challenge_outcomes WHERE media_id IN ({mid_ph})", media_ids)
conn.execute(f"DELETE FROM media WHERE id IN ({mid_ph})", media_ids)
conn.commit()
Expand Down
2 changes: 1 addition & 1 deletion gas/cache/media_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ def retrieve_media(
mask = None
mask_path = None
if kwargs.get("require_mask", False):
mask_path = file_path.with_suffix("_mask.npy")
mask_path = file_path.parent / (file_path.stem + "_mask.npy")
mask = self.read_mask(mask_path)
if mask is None:
print(f"Mask not found for {file_path}")
Expand Down
28 changes: 23 additions & 5 deletions gas/generation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,28 @@

Re-exports below preserve the historical flat import paths so external
callers can keep using `from gas.generation import PromptGenerator` etc.
They are resolved lazily (PEP 562) so that importing a pure-python
submodule (e.g. gas.generation.prompts.prompt_qc) does not drag in the
torch/diffusers/janus runtime stack.
"""

from .media.generation_pipeline import GenerationPipeline
from .media.models import initialize_model_registry
from .prompts.model_prompt_styles import AdaptedPrompt, adapt_for_local_model
from .prompts.prompt_generator import PromptGenerator
from .prompts.scene import SceneDescription, extract_scene_with_vlm
_LAZY_EXPORTS = {
"GenerationPipeline": "gas.generation.media.generation_pipeline",
"initialize_model_registry": "gas.generation.media.models",
"AdaptedPrompt": "gas.generation.prompts.model_prompt_styles",
"adapt_for_local_model": "gas.generation.prompts.model_prompt_styles",
"PromptGenerator": "gas.generation.prompts.prompt_generator",
"SceneDescription": "gas.generation.prompts.scene",
"extract_scene_with_vlm": "gas.generation.prompts.scene",
}

__all__ = list(_LAZY_EXPORTS)


def __getattr__(name):
module_path = _LAZY_EXPORTS.get(name)
if module_path is None:
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
import importlib

return getattr(importlib.import_module(module_path), name)
3 changes: 1 addition & 2 deletions gas/generation/media/generation_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,12 +240,11 @@ def _generate_media_with_model(self, model_name, prompt, image):

if task == "i2i" and image is None:
raise ValueError(
"An image must be provided for image-to-image model {model_name}"
f"An image must be provided for image-to-image model {model_name}"
)

if not self._load_model(model_name):
raise RuntimeError(f"Failed to load {model_name}")
return {}

bt.logging.debug("Preparing generation arguments")
gen_args = model_config.get("generation_args", {}).copy()
Expand Down
Loading
Loading