Spaces:
Sleeping
Comprehensive fix: newer dep stack + HF-dataset cache + verified locally
Browse filesDep cascade resolved by jumping forward instead of holding back:
- spaces/train/Dockerfile: pytorch:2.6.0-cuda12.4-cudnn9-runtime base
(has torch.int1 needed by torchao>=0.16). Smoke-test layer asserts
torch.int1 presence + imports torch/transformers/peft/trl/
GRPOTrainer/GRPOConfig/AutoTokenizer at build time, so any future
dep mismatch fails the build in 30s instead of crashing phase 3.
- pyproject.toml [train] extras: trl>=0.22, transformers>=4.55,
peft>=0.16, accelerate>=1.6, bitsandbytes>=0.45, torchao>=0.16,
huggingface_hub>=1.0. Verified locally: torch 2.10 / transformers
5.6 / peft 0.19 / trl 1.2 all import cleanly and Gemma-4 tokenizer
loads (the bug that crashed phase 3).
Cache architecture migrated from in-repo to HF Hub dataset:
- .gitignore: revert to ignoring .cache/ entirely.
- root Dockerfile: drop COPY .cache/ β env Space pulls from hub.
- app/main.py: lifespan startup hook _hydrate_cache_from_hub() calls
snapshot_download(STOCKER_CACHE_REPO) when env var is set. Idempotent,
never crashes startup. No-op locally without the env var.
- scripts/upload_cache.py: new utility (HfApi.upload_folder) to push
.cache/council to Hydr473/stocker-cache after running precache.
- .github/workflows/deploy_spaces.yml: STOCKER_CACHE_REPO added to
workflow env block + pushed as Space secret in deploy-env-space.
- README: paragraph documenting precache β upload β restart workflow.
Verified locally before pushing:
- 22/22 pytest pass with new dep set
- AutoTokenizer.from_pretrained('google/gemma-4-E4B-it') succeeds
- create_app() spawns 22 routes (includes /, /web, /blog, /council,
/reset, /step, /state, /meta, /ohlcv, /training/runs, /training/metrics)
- torch.int1 present, all imports clean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .github/workflows/deploy_spaces.yml +8 -7
- .gitignore +3 -7
- Dockerfile +0 -4
- README.md +20 -0
- app/main.py +37 -0
- pyproject.toml +11 -9
- scripts/upload_cache.py +57 -0
- spaces/train/Dockerfile +33 -28
|
@@ -7,12 +7,13 @@ on:
|
|
| 7 |
# Non-sensitive config β hardcoded here so the only required GitHub secret
|
| 8 |
# is HF_TOKEN. Edit these in-place if your endpoint URL or model changes.
|
| 9 |
env:
|
| 10 |
-
HF_USERNAME:
|
| 11 |
-
ENV_SPACE:
|
| 12 |
-
TRAIN_SPACE:
|
| 13 |
-
API_BASE_URL:
|
| 14 |
-
MODEL_NAME:
|
| 15 |
-
RESULTS_REPO:
|
|
|
|
| 16 |
|
| 17 |
jobs:
|
| 18 |
deploy-env-space:
|
|
@@ -36,7 +37,7 @@ jobs:
|
|
| 36 |
repo = os.environ["ENV_SPACE"]
|
| 37 |
api.create_repo(repo_id=repo, repo_type="space", space_sdk="docker", exist_ok=True)
|
| 38 |
# Optional β env Space only needs LLM creds if /inference is hit
|
| 39 |
-
for k in ("HF_TOKEN", "API_BASE_URL", "MODEL_NAME"):
|
| 40 |
v = os.environ.get(k, "")
|
| 41 |
if v:
|
| 42 |
api.add_space_secret(repo_id=repo, key=k, value=v)
|
|
|
|
| 7 |
# Non-sensitive config β hardcoded here so the only required GitHub secret
|
| 8 |
# is HF_TOKEN. Edit these in-place if your endpoint URL or model changes.
|
| 9 |
env:
|
| 10 |
+
HF_USERNAME: Hydr473
|
| 11 |
+
ENV_SPACE: Hydr473/stocker-env
|
| 12 |
+
TRAIN_SPACE: Hydr473/stocker-train
|
| 13 |
+
API_BASE_URL: https://at0e6z2u64774tc7.us-east-1.aws.endpoints.huggingface.cloud/v1
|
| 14 |
+
MODEL_NAME: ggml-org/gemma-4-26B-A4B-it-GGUF
|
| 15 |
+
RESULTS_REPO: Hydr473/stocker-results
|
| 16 |
+
STOCKER_CACHE_REPO: Hydr473/stocker-cache
|
| 17 |
|
| 18 |
jobs:
|
| 19 |
deploy-env-space:
|
|
|
|
| 37 |
repo = os.environ["ENV_SPACE"]
|
| 38 |
api.create_repo(repo_id=repo, repo_type="space", space_sdk="docker", exist_ok=True)
|
| 39 |
# Optional β env Space only needs LLM creds if /inference is hit
|
| 40 |
+
for k in ("HF_TOKEN", "API_BASE_URL", "MODEL_NAME", "STOCKER_CACHE_REPO"):
|
| 41 |
v = os.environ.get(k, "")
|
| 42 |
if v:
|
| 43 |
api.add_space_secret(repo_id=repo, key=k, value=v)
|
|
@@ -32,13 +32,9 @@ coverage.xml
|
|
| 32 |
.nox/
|
| 33 |
|
| 34 |
# --- Stocker runtime artifacts --------------------------------------------
|
| 35 |
-
# Council cache
|
| 36 |
-
#
|
| 37 |
-
|
| 38 |
-
.cache/*
|
| 39 |
-
!.cache/council/
|
| 40 |
-
.cache/council/*/lora_*/
|
| 41 |
-
.cache/moderator/
|
| 42 |
# GRPO runs, eval rollouts, plots, LoRA adapters
|
| 43 |
training/runs/
|
| 44 |
!training/runs/.gitkeep
|
|
|
|
| 32 |
.nox/
|
| 33 |
|
| 34 |
# --- Stocker runtime artifacts --------------------------------------------
|
| 35 |
+
# Council vote cache lives in an HF dataset repo (Hydr473/stocker-cache),
|
| 36 |
+
# downloaded at Space startup. Never commit it to git.
|
| 37 |
+
.cache/
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
# GRPO runs, eval rollouts, plots, LoRA adapters
|
| 39 |
training/runs/
|
| 40 |
!training/runs/.gitkeep
|
|
@@ -24,10 +24,6 @@ COPY server/ /app/server/
|
|
| 24 |
COPY data/ /app/data/
|
| 25 |
COPY inference.py client.py README.md /app/
|
| 26 |
|
| 27 |
-
# Pre-built council cache β gives judges instant UI responses for the 3
|
| 28 |
-
# stable tasks. Optional: COPY only fires if .cache/ exists in build context.
|
| 29 |
-
COPY .cache/ /app/.cache/
|
| 30 |
-
|
| 31 |
# Pull in the built frontend so FastAPI mounts the React SPA at /web
|
| 32 |
COPY --from=frontend-builder /frontend/dist /app/frontend/dist
|
| 33 |
|
|
|
|
| 24 |
COPY data/ /app/data/
|
| 25 |
COPY inference.py client.py README.md /app/
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
# Pull in the built frontend so FastAPI mounts the React SPA at /web
|
| 28 |
COPY --from=frontend-builder /frontend/dist /app/frontend/dist
|
| 29 |
|
|
@@ -265,6 +265,26 @@ pytest tests/ -q
|
|
| 265 |
To run the council against your own endpoint, set `API_BASE_URL`,
|
| 266 |
`MODEL_NAME`, and `HF_TOKEN` (see [.env.example](.env.example)).
|
| 267 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
---
|
| 269 |
|
| 270 |
## Evaluation
|
|
|
|
| 265 |
To run the council against your own endpoint, set `API_BASE_URL`,
|
| 266 |
`MODEL_NAME`, and `HF_TOKEN` (see [.env.example](.env.example)).
|
| 267 |
|
| 268 |
+
### Council vote cache
|
| 269 |
+
|
| 270 |
+
Each `(role, ticker, date)` triple is computed once and cached as JSON. The
|
| 271 |
+
env Space pulls a pre-warmed cache from the HF dataset
|
| 272 |
+
[Hydr473/stocker-cache](https://huggingface.co/datasets/Hydr473/stocker-cache)
|
| 273 |
+
on startup so judges' UI clicks are instant. To re-warm:
|
| 274 |
+
|
| 275 |
+
```bash
|
| 276 |
+
# 1. Compute votes via your endpoint (writes .cache/council/<role>/base/...)
|
| 277 |
+
python scripts/precache_endpoint.py --tasks task_easy,task_medium,task_hard
|
| 278 |
+
|
| 279 |
+
# 2. Upload to the dataset repo
|
| 280 |
+
python scripts/upload_cache.py # uses $STOCKER_CACHE_REPO or default
|
| 281 |
+
|
| 282 |
+
# 3. Restart the env Space β its lifespan hook downloads the new cache.
|
| 283 |
+
```
|
| 284 |
+
|
| 285 |
+
The startup hook is no-op (and never crashes) when `STOCKER_CACHE_REPO` is
|
| 286 |
+
unset, so local dev still works without HF Hub access.
|
| 287 |
+
|
| 288 |
---
|
| 289 |
|
| 290 |
## Evaluation
|
|
@@ -3,9 +3,11 @@
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import logging
|
|
|
|
| 6 |
import sys
|
| 7 |
import time
|
| 8 |
import traceback
|
|
|
|
| 9 |
from pathlib import Path
|
| 10 |
|
| 11 |
from fastapi import FastAPI, Request
|
|
@@ -19,6 +21,7 @@ from app.config import settings
|
|
| 19 |
|
| 20 |
FRONTEND_DIST = Path(__file__).resolve().parents[1] / "frontend" / "dist"
|
| 21 |
TRAINING_RUNS = Path(__file__).resolve().parents[1] / "training" / "runs"
|
|
|
|
| 22 |
|
| 23 |
logging.basicConfig(
|
| 24 |
level=logging.INFO,
|
|
@@ -28,6 +31,39 @@ logging.basicConfig(
|
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
def create_app() -> FastAPI:
|
| 32 |
app = FastAPI(
|
| 33 |
title="Stocker - OpenEnv",
|
|
@@ -36,6 +72,7 @@ def create_app() -> FastAPI:
|
|
| 36 |
openapi_url="/openapi.json",
|
| 37 |
docs_url="/docs",
|
| 38 |
redoc_url="/redoc",
|
|
|
|
| 39 |
)
|
| 40 |
|
| 41 |
@app.exception_handler(Exception)
|
|
|
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
import logging
|
| 6 |
+
import os
|
| 7 |
import sys
|
| 8 |
import time
|
| 9 |
import traceback
|
| 10 |
+
from contextlib import asynccontextmanager
|
| 11 |
from pathlib import Path
|
| 12 |
|
| 13 |
from fastapi import FastAPI, Request
|
|
|
|
| 21 |
|
| 22 |
FRONTEND_DIST = Path(__file__).resolve().parents[1] / "frontend" / "dist"
|
| 23 |
TRAINING_RUNS = Path(__file__).resolve().parents[1] / "training" / "runs"
|
| 24 |
+
CACHE_DIR = Path(__file__).resolve().parents[1] / ".cache"
|
| 25 |
|
| 26 |
logging.basicConfig(
|
| 27 |
level=logging.INFO,
|
|
|
|
| 31 |
logger = logging.getLogger(__name__)
|
| 32 |
|
| 33 |
|
| 34 |
+
def _hydrate_cache_from_hub() -> None:
|
| 35 |
+
"""If STOCKER_CACHE_REPO is set, download the cache from that HF dataset
|
| 36 |
+
into .cache/ on startup. Idempotent β skipped when .cache/ already has
|
| 37 |
+
council/ entries. Never fails startup; logs and continues on error."""
|
| 38 |
+
repo = os.getenv("STOCKER_CACHE_REPO", "").strip()
|
| 39 |
+
if not repo:
|
| 40 |
+
return
|
| 41 |
+
if (CACHE_DIR / "council").exists() and any((CACHE_DIR / "council").iterdir()):
|
| 42 |
+
logger.info("Cache already populated; skipping hub download.")
|
| 43 |
+
return
|
| 44 |
+
try:
|
| 45 |
+
from huggingface_hub import snapshot_download
|
| 46 |
+
token = os.getenv("HF_TOKEN") or None
|
| 47 |
+
logger.info("Downloading council cache from %s ...", repo)
|
| 48 |
+
snapshot_download(
|
| 49 |
+
repo_id=repo,
|
| 50 |
+
repo_type="dataset",
|
| 51 |
+
local_dir=str(CACHE_DIR),
|
| 52 |
+
token=token,
|
| 53 |
+
allow_patterns=["council/**/*.json"],
|
| 54 |
+
)
|
| 55 |
+
n = sum(1 for _ in CACHE_DIR.rglob("*.json"))
|
| 56 |
+
logger.info("Cache hydrated: %d entries from %s", n, repo)
|
| 57 |
+
except Exception as e:
|
| 58 |
+
logger.warning("Cache hydration failed (%s); will fall back to live calls / mock.", e)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@asynccontextmanager
|
| 62 |
+
async def lifespan(app: FastAPI):
|
| 63 |
+
_hydrate_cache_from_hub()
|
| 64 |
+
yield
|
| 65 |
+
|
| 66 |
+
|
| 67 |
def create_app() -> FastAPI:
|
| 68 |
app = FastAPI(
|
| 69 |
title="Stocker - OpenEnv",
|
|
|
|
| 72 |
openapi_url="/openapi.json",
|
| 73 |
docs_url="/docs",
|
| 74 |
redoc_url="/redoc",
|
| 75 |
+
lifespan=lifespan,
|
| 76 |
)
|
| 77 |
|
| 78 |
@app.exception_handler(Exception)
|
|
@@ -39,17 +39,19 @@ serve = [
|
|
| 39 |
"vllm>=0.6.0",
|
| 40 |
]
|
| 41 |
train = [
|
| 42 |
-
|
| 43 |
-
#
|
| 44 |
-
#
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
"peft>=0.
|
| 49 |
-
"accelerate>=1.
|
| 50 |
-
"bitsandbytes>=0.
|
| 51 |
"datasets>=3.0.0",
|
| 52 |
"tensorboard>=2.18.0",
|
|
|
|
|
|
|
| 53 |
]
|
| 54 |
|
| 55 |
[tool.setuptools]
|
|
|
|
| 39 |
"vllm>=0.6.0",
|
| 40 |
]
|
| 41 |
train = [
|
| 42 |
+
# Newer coherent stack: torch 2.6 (has torch.int1) β torchao>=0.16 β
|
| 43 |
+
# peft>=0.13 (LoRA-injection probe) β transformers>=4.55 (Gemma-4
|
| 44 |
+
# tokenizer fix, has BloomPreTrainedModel only via lazy import which
|
| 45 |
+
# works) β trl>=0.22 β huggingface_hub>=1.0.
|
| 46 |
+
"trl>=0.22.0",
|
| 47 |
+
"transformers>=4.55.0",
|
| 48 |
+
"peft>=0.16.0",
|
| 49 |
+
"accelerate>=1.6.0",
|
| 50 |
+
"bitsandbytes>=0.45.0",
|
| 51 |
"datasets>=3.0.0",
|
| 52 |
"tensorboard>=2.18.0",
|
| 53 |
+
"torchao>=0.16.0",
|
| 54 |
+
"huggingface_hub>=1.0.0",
|
| 55 |
]
|
| 56 |
|
| 57 |
[tool.setuptools]
|
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Push the local .cache/council/ directory to an HF dataset repo so the
|
| 3 |
+
env Space can download it at startup (instead of bundling in git).
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
export HF_TOKEN=hf_xxx
|
| 7 |
+
python scripts/upload_cache.py # default repo
|
| 8 |
+
python scripts/upload_cache.py --repo Hydr473/stocker-cache
|
| 9 |
+
"""
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import os
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 18 |
+
CACHE_DIR = ROOT / ".cache"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def main() -> None:
|
| 22 |
+
p = argparse.ArgumentParser()
|
| 23 |
+
p.add_argument("--repo", default=os.getenv("STOCKER_CACHE_REPO", "Hydr473/stocker-cache"),
|
| 24 |
+
help="HF dataset repo (default: Hydr473/stocker-cache)")
|
| 25 |
+
p.add_argument("--token", default=os.getenv("HF_TOKEN"),
|
| 26 |
+
help="HF write token (default: $HF_TOKEN env)")
|
| 27 |
+
args = p.parse_args()
|
| 28 |
+
|
| 29 |
+
if not args.token:
|
| 30 |
+
sys.exit("HF_TOKEN not set (and no --token provided). Aborting.")
|
| 31 |
+
|
| 32 |
+
if not (CACHE_DIR / "council").is_dir():
|
| 33 |
+
sys.exit(f"No cache to upload β {CACHE_DIR/'council'} doesn't exist. "
|
| 34 |
+
"Run scripts/precache_endpoint.py first.")
|
| 35 |
+
|
| 36 |
+
n_files = sum(1 for _ in (CACHE_DIR / "council").rglob("*.json"))
|
| 37 |
+
if n_files == 0:
|
| 38 |
+
sys.exit("Cache directory is empty. Run scripts/precache_endpoint.py first.")
|
| 39 |
+
|
| 40 |
+
from huggingface_hub import HfApi
|
| 41 |
+
|
| 42 |
+
api = HfApi(token=args.token)
|
| 43 |
+
api.create_repo(repo_id=args.repo, repo_type="dataset", exist_ok=True)
|
| 44 |
+
print(f"Uploading {n_files} cache entries β https://huggingface.co/datasets/{args.repo}")
|
| 45 |
+
|
| 46 |
+
api.upload_folder(
|
| 47 |
+
folder_path=str(CACHE_DIR),
|
| 48 |
+
repo_id=args.repo,
|
| 49 |
+
repo_type="dataset",
|
| 50 |
+
allow_patterns=["council/**/*.json"],
|
| 51 |
+
commit_message=f"Update council cache ({n_files} entries)",
|
| 52 |
+
)
|
| 53 |
+
print(f"Done. The env Space will pull this on next startup if STOCKER_CACHE_REPO={args.repo}.")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
if __name__ == "__main__":
|
| 57 |
+
main()
|
|
@@ -10,13 +10,14 @@
|
|
| 10 |
# Layer order is deliberate:
|
| 11 |
# 1. OS packages (rare changes β cached forever)
|
| 12 |
# 2. Heavy pip install (cached unless THIS Dockerfile changes)
|
| 13 |
-
# 3.
|
| 14 |
-
# 4.
|
| 15 |
-
# 5.
|
| 16 |
-
# 6.
|
| 17 |
-
#
|
|
|
|
| 18 |
|
| 19 |
-
FROM pytorch/pytorch:2.
|
| 20 |
|
| 21 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 22 |
PYTHONUNBUFFERED=1 \
|
|
@@ -31,16 +32,11 @@ RUN apt-get update -qq && \
|
|
| 31 |
rm -rf /var/lib/apt/lists/*
|
| 32 |
|
| 33 |
# βββ Layer 2: heavy pip install (cached) ββββββββββββββββββββββββββββββββ
|
| 34 |
-
#
|
| 35 |
-
#
|
| 36 |
-
#
|
| 37 |
-
#
|
| 38 |
-
#
|
| 39 |
-
# satisfy transformers' constraint.
|
| 40 |
-
# NOTE: gradio is intentionally NOT installed here. gradio<5 pulls
|
| 41 |
-
# gradio-client==1.3.0 which constrains websockets<13, but openenv-core
|
| 42 |
-
# requires websockets>=15.0.1 β incompatible. The training Space UI uses
|
| 43 |
-
# minimal FastAPI + HTML instead (see spaces/train/app.py).
|
| 44 |
RUN pip install --no-cache-dir uv && \
|
| 45 |
uv pip install --system --no-cache \
|
| 46 |
"fastapi>=0.115.0" "uvicorn[standard]>=0.30.0" \
|
|
@@ -48,27 +44,36 @@ RUN pip install --no-cache-dir uv && \
|
|
| 48 |
"openai>=1.0.0" "openenv-core>=0.2.0" \
|
| 49 |
"pandas>=2.0.0" "pyarrow>=14.0.0" "pillow>=10.0.0" "markdown-it-py>=3.0.0" \
|
| 50 |
"yfinance>=0.2.0" "mplfinance>=0.12.0" "matplotlib>=3.8.0" \
|
| 51 |
-
"trl>=0.
|
| 52 |
-
"
|
| 53 |
-
"
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
#
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
| 59 |
import huggingface_hub as hub; \
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
-
# βββ Layer
|
| 63 |
ADD https://api.github.com/repos/CRIMSONHydra/stocker/commits/main /tmp/.commit-sha
|
| 64 |
|
| 65 |
-
# βββ Layer
|
| 66 |
RUN git clone --depth=1 https://github.com/CRIMSONHydra/stocker.git /app
|
| 67 |
|
| 68 |
-
# βββ Layer
|
| 69 |
RUN cd /app && pip install --no-deps -e .
|
| 70 |
|
| 71 |
-
# βββ Layer
|
| 72 |
RUN python scripts/build_dataset.py && python scripts/build_ideal_profit.py
|
| 73 |
|
| 74 |
EXPOSE 7860
|
|
|
|
| 10 |
# Layer order is deliberate:
|
| 11 |
# 1. OS packages (rare changes β cached forever)
|
| 12 |
# 2. Heavy pip install (cached unless THIS Dockerfile changes)
|
| 13 |
+
# 3. Smoke test (fails build immediately on dep mismatch)
|
| 14 |
+
# 4. ADD commits/main (cache-busts everything below)
|
| 15 |
+
# 5. git clone (~10s)
|
| 16 |
+
# 6. editable install (~5s)
|
| 17 |
+
# 7. dataset build (~30s)
|
| 18 |
+
# Normal git push triggers only steps 5-7 (~45s total).
|
| 19 |
|
| 20 |
+
FROM pytorch/pytorch:2.6.0-cuda12.4-cudnn9-runtime
|
| 21 |
|
| 22 |
ENV PYTHONDONTWRITEBYTECODE=1 \
|
| 23 |
PYTHONUNBUFFERED=1 \
|
|
|
|
| 32 |
rm -rf /var/lib/apt/lists/*
|
| 33 |
|
| 34 |
# βββ Layer 2: heavy pip install (cached) ββββββββββββββββββββββββββββββββ
|
| 35 |
+
# Newer coherent stack: torch 2.6 (has torch.int1) β torchao>=0.16 β
|
| 36 |
+
# peft>=0.16 (LoRA-injection probe satisfied) β transformers>=4.55
|
| 37 |
+
# (correct Gemma-4 tokenizer behavior) β trl>=0.22 β huggingface_hub>=1.0.
|
| 38 |
+
# Gradio is NOT installed β it conflicts with openenv-core's websockets>=15.
|
| 39 |
+
# The training Space UI uses minimal FastAPI + HTML (spaces/train/app.py).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
RUN pip install --no-cache-dir uv && \
|
| 41 |
uv pip install --system --no-cache \
|
| 42 |
"fastapi>=0.115.0" "uvicorn[standard]>=0.30.0" \
|
|
|
|
| 44 |
"openai>=1.0.0" "openenv-core>=0.2.0" \
|
| 45 |
"pandas>=2.0.0" "pyarrow>=14.0.0" "pillow>=10.0.0" "markdown-it-py>=3.0.0" \
|
| 46 |
"yfinance>=0.2.0" "mplfinance>=0.12.0" "matplotlib>=3.8.0" \
|
| 47 |
+
"trl>=0.22.0" "transformers>=4.55.0" "peft>=0.16.0" \
|
| 48 |
+
"accelerate>=1.6.0" "bitsandbytes>=0.45.0" "datasets>=3.0.0" \
|
| 49 |
+
"tensorboard>=2.18.0" "torchao>=0.16.0" "huggingface_hub>=1.0.0"
|
| 50 |
|
| 51 |
+
# βββ Layer 3: build-time smoke test ββββββββββββββββββββββββββββββββββββ
|
| 52 |
+
# If any dep combo is broken (import error, version mismatch, missing
|
| 53 |
+
# attribute, tokenizer regression), this fails at BUILD time in 30 sec
|
| 54 |
+
# instead of crashing 25 minutes into phase 3 of training.
|
| 55 |
+
RUN python -c "\
|
| 56 |
+
import torch, transformers, peft, trl, bitsandbytes, accelerate; \
|
| 57 |
import huggingface_hub as hub; \
|
| 58 |
+
from peft import LoraConfig; \
|
| 59 |
+
from trl import GRPOConfig, GRPOTrainer; \
|
| 60 |
+
from transformers import AutoTokenizer; \
|
| 61 |
+
print(f'torch={torch.__version__} hub={hub.__version__}'); \
|
| 62 |
+
print(f'transformers={transformers.__version__} peft={peft.__version__} trl={trl.__version__}'); \
|
| 63 |
+
assert hasattr(torch, 'int1'), 'torch.int1 missing β torchao>=0.16 will crash'; \
|
| 64 |
+
print('smoke test passed')\
|
| 65 |
+
"
|
| 66 |
|
| 67 |
+
# βββ Layer 4: cache-bust on every commit to main ββββββββββββββββββββββββ
|
| 68 |
ADD https://api.github.com/repos/CRIMSONHydra/stocker/commits/main /tmp/.commit-sha
|
| 69 |
|
| 70 |
+
# βββ Layer 5: fresh clone of the repo βββββββββββββββββββββββββββββββββββ
|
| 71 |
RUN git clone --depth=1 https://github.com/CRIMSONHydra/stocker.git /app
|
| 72 |
|
| 73 |
+
# βββ Layer 6: register the local package (no dep resolution) ββββββββββββ
|
| 74 |
RUN cd /app && pip install --no-deps -e .
|
| 75 |
|
| 76 |
+
# βββ Layer 7: build the bundled dataset βββββββββββββββββββββββββββββββββ
|
| 77 |
RUN python scripts/build_dataset.py && python scripts/build_ideal_profit.py
|
| 78 |
|
| 79 |
EXPOSE 7860
|