Hydr473 Claude Sonnet 4.6 commited on
Commit
508cb6a
Β·
1 Parent(s): c41ab72

Comprehensive fix: newer dep stack + HF-dataset cache + verified locally

Browse files

Dep 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 CHANGED
@@ -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: 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
 
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)
.gitignore CHANGED
@@ -32,13 +32,9 @@ coverage.xml
32
  .nox/
33
 
34
  # --- Stocker runtime artifacts --------------------------------------------
35
- # Council cache: ignore everything under .cache/ EXCEPT the demo bundle
36
- # (specialists/base for the 3 stable tasks). Allows shipping a warm cache
37
- # in the env Space so judges' UI clicks hit instant responses.
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
Dockerfile CHANGED
@@ -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
 
README.md CHANGED
@@ -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
app/main.py CHANGED
@@ -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)
pyproject.toml CHANGED
@@ -39,17 +39,19 @@ serve = [
39
  "vllm>=0.6.0",
40
  ]
41
  train = [
42
- "trl>=0.11.0,<0.20.0",
43
- # transformers <4.50 avoids the torchao quant import path that hits
44
- # `torch.int1` (added in torch 2.6). Matches torch 2.5.1 base image.
45
- "transformers>=4.45.0,<4.50.0",
46
- # peft <0.13 doesn't probe torchao at LoRA-injection time, so it works
47
- # without torchao>=0.16. Stays compatible with torch 2.5.1.
48
- "peft>=0.11.0,<0.13.0",
49
- "accelerate>=1.0.0",
50
- "bitsandbytes>=0.43.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]
scripts/upload_cache.py ADDED
@@ -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()
spaces/train/Dockerfile CHANGED
@@ -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. ADD commits/main (cache-busts everything below)
14
- # 4. git clone (~10s)
15
- # 5. editable install (~5s)
16
- # 6. dataset build (~30s)
17
- # So a normal git push only triggers steps 4-6 (~45s total).
 
18
 
19
- FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
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
- # Pinned for torch 2.5.1 compatibility:
35
- # transformers<4.50 β†’ avoids torch.int1 import path in newer torchao
36
- # peft<0.13 β†’ avoids the torchao>=0.16 LoRA-injection probe
37
- # trl<0.20 β†’ compatible with peft<0.13
38
- # huggingface_hub is intentionally NOT pinned β€” pip resolves to <1.0 to
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.11.0,<0.20.0" "transformers>=4.45.0,<4.50.0" \
52
- "peft>=0.11.0,<0.13.0" "accelerate>=1.0.0" "bitsandbytes>=0.43.0" \
53
- "datasets>=3.0.0" "tensorboard>=2.18.0"
54
 
55
- # Fail-fast smoke test: if dep resolution produced an incompatible set,
56
- # this layer fails the build immediately instead of crashing at phase 3.
57
- RUN python -c "import torch, transformers, peft, trl, bitsandbytes, accelerate; \
58
- from peft import LoraConfig; from trl import GRPOConfig, GRPOTrainer; \
 
 
59
  import huggingface_hub as hub; \
60
- print(f'OK torch={torch.__version__} transformers={transformers.__version__} peft={peft.__version__} trl={trl.__version__} hub={hub.__version__}')"
 
 
 
 
 
 
 
61
 
62
- # ─── Layer 3: cache-bust on every commit to main ────────────────────────
63
  ADD https://api.github.com/repos/CRIMSONHydra/stocker/commits/main /tmp/.commit-sha
64
 
65
- # ─── Layer 4: fresh clone of the repo ───────────────────────────────────
66
  RUN git clone --depth=1 https://github.com/CRIMSONHydra/stocker.git /app
67
 
68
- # ─── Layer 5: register the local package (no dep resolution) ────────────
69
  RUN cd /app && pip install --no-deps -e .
70
 
71
- # ─── Layer 6: build the bundled dataset ─────────────────────────────────
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