Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- Blog.md +58 -0
- Dockerfile +13 -3
- README.md +4 -1
- inference.py +2 -1
- pyproject.toml +23 -2
- rollout_baseline.py +139 -0
- start.sh +29 -0
- test_env_smoke.py +118 -0
- train_jewelry_grpo.py +363 -0
- training/__init__.py +5 -0
- training/parse_action.py +52 -0
- training/plotting.py +325 -0
- training/prompts.py +169 -0
- training/rewards.py +48 -0
- training/rollout.py +214 -0
- training/training_artifacts_v1/loss_curve.png +0 -0
- training/training_artifacts_v1/metrics.csv +20 -0
- training/training_artifacts_v1/metrics.json +695 -0
- training/training_artifacts_v1/reward_curve.png +0 -0
- training/training_artifacts_v1/reward_total_curve.png +0 -0
- training/training_artifacts_v1/training_summary.json +63 -0
- ui.py +358 -0
- uv.lock +0 -0
Blog.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏬 ShopManagerEng: Training LLMs to Run a Business
|
| 2 |
+
|
| 3 |
+
Welcome to **ShopManagerEng**, a Reinforcement Learning (RL) environment designed to test and train Large Language Models (LLMs) on complex, multi-step business operations.
|
| 4 |
+
|
| 5 |
+
While LLMs have become incredibly adept at chatting and writing code, evaluating their ability to make strategic, long-term decisions in a dynamic environment remains a challenge. ShopManagerEng tackles this by putting the AI in the shoes of a jewelry shop manager. Dont get into name 'Eng',just typos mistake of 'Env'.
|
| 6 |
+
|
| 7 |
+
This blog post breaks down how the environment works, its architecture, and how it can be used to train smarter, more strategic agents.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 🎮 The Environment: A 3-Phase Business Simulation
|
| 12 |
+
|
| 13 |
+
ShopManagerEng is not a simple Q&A benchmark. It is a continuous simulation where the agent must manage inventory, respond to market fluctuations, and negotiate with customers. Every "episode" (a full game loop) consists of three distinct phases:
|
| 14 |
+
|
| 15 |
+
### Phase 1: 📈 The Market (Supply)
|
| 16 |
+
The agent starts with a limited budget and must decide when to buy raw gold.
|
| 17 |
+
* **The Catch:** Gold prices fluctuate. The agent can observe price trends and decide to "wait" for a better price or "buy" immediately.
|
| 18 |
+
* **Real-World Noise:** The environment supports a "real" market mode that pulls live gold prices from Yahoo Finance, testing the agent against real-world economic volatility.
|
| 19 |
+
|
| 20 |
+
### Phase 2: 🏭 The Warehouse (Production)
|
| 21 |
+
Once gold is acquired, the agent must decide what to craft: Rings, Necklaces, or Bracelets.
|
| 22 |
+
* **The Catch:** The agent must balance raw material costs, labor costs, and market demand. It receives a "demand forecast" (which includes noise) and must make production choices that maximize potential profit without running out of cash or gold.
|
| 23 |
+
|
| 24 |
+
### Phase 3: 🤝 The Showroom (Sales)
|
| 25 |
+
The final phase tests the agent's negotiation skills.
|
| 26 |
+
* **The Catch:** Customers make initial offers. The agent can accept, reject, or counter-offer over a maximum of 5 rounds. Push too hard, and the customer walks away (resulting in zero profit). Settle too early, and the agent leaves money on the table.
|
| 27 |
+
|
| 28 |
+
The agent's final score is a cumulative reward based on how well it navigated all three phases.
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## 🏗️ Architecture Under the Hood
|
| 33 |
+
|
| 34 |
+
ShopManagerEng is built on a robust Client-Server architecture, making it highly scalable for Reinforcement Learning tasks.
|
| 35 |
+
|
| 36 |
+
1. **The Server (`/server/`):** The core simulation engine runs as a standalone web application (often deployed via Docker to a Hugging Face Space). It tracks the hidden "true" state of the world, handles state transitions, fetches real market data, and manages an SQLite database to log inventory and invoices.
|
| 37 |
+
2. **The Client (`client.py` & `models.py`):** Provides a clean, typed Python interface. Agents use this client to send actions (e.g., `{"market_action": "buy", "gold_qty": 2.0}`) and receive structured observations in return.
|
| 38 |
+
3. **The AI Player (`inference.py`):** A script demonstrating how to plug an LLM (like Llama 3) into the environment. It dynamically builds text prompts explaining the current state (e.g., *"Gold is $2000/oz and trending down. You have $1000. Buy or wait?"*) and parses the LLM's text output back into valid game actions.
|
| 39 |
+
4. **The Training Suite (`/training/`):** The real power of this project lies in its RL training capabilities. Using algorithms like GRPO (Group Relative Policy Optimization), the framework can run massive batches of episodes, evaluate the agent using custom reward functions, and iteratively update the model's weights to forge a master negotiator and supply-chain manager.
|
| 40 |
+
|
| 41 |
+
### 🛠️ Technical Stack & Data Flow
|
| 42 |
+
|
| 43 |
+
* **FastAPI & OpenEnv:** The server is powered by FastAPI, leveraging the `openenv` framework to standardize interactions. This ensures the environment can be easily hosted (e.g., on Hugging Face Spaces) and queried by agents remotely.
|
| 44 |
+
* **Pydantic Models:** All actions and observations are strongly typed using Pydantic (`models.py`). This guarantees that agents send properly formatted JSON payloads (like `target_price_usd` or `inventory_urgent` flags) and receive structured state data.
|
| 45 |
+
* **State Persistence:** A built-in SQLite store (`sqlite_store.py`) tracks the complex state across episodes, maintaining ledgers for cash, gold (in troy ounces and grams), and specific inventory items (rings, necklaces, bracelets).
|
| 46 |
+
* **yfinance Integration:** For the "real" market mode, the environment dynamically fetches live GC=F (Gold Futures) ticker data via `market_data.py`, injecting true market volatility into the simulation rather than relying purely on synthetic random walks.
|
| 47 |
+
* **GRPO Training:** The training pipeline (`train_jewelry_grpo.py`) utilizes Group Relative Policy Optimization, an advanced RL technique designed to stabilize training and improve sample efficiency when teaching LLMs complex, multi-step tasks. Custom reward functions (`rewards.py`) evaluate and guide the model's behavior.
|
| 48 |
+
|
| 49 |
+
---
|
| 50 |
+
|
| 51 |
+
## 🚀 Why This Matters
|
| 52 |
+
|
| 53 |
+
As we move towards autonomous AI agents, we need benchmarks that go beyond static knowledge retrieval. ShopManagerEng evaluates an agent's ability to:
|
| 54 |
+
* **Plan Long-Term:** A bad purchase in Phase 1 ruins the negotiation in Phase 3.
|
| 55 |
+
* **Handle Uncertainty:** Dealing with noisy demand forecasts and volatile live markets.
|
| 56 |
+
* **Negotiate:** Understanding margins and customer psychology.
|
| 57 |
+
|
| 58 |
+
Whether you are testing the out-of-the-box reasoning of a new foundational model or fine-tuning a specialized RL agent, ShopManagerEng provides a rich, complex sandbox to push AI capabilities forward.
|
Dockerfile
CHANGED
|
@@ -59,6 +59,11 @@ FROM ${BASE_IMAGE}
|
|
| 59 |
|
| 60 |
WORKDIR /app
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
# Copy the virtual environment from builder
|
| 63 |
COPY --from=builder /app/env/.venv /app/.venv
|
| 64 |
|
|
@@ -71,11 +76,16 @@ ENV PATH="/app/.venv/bin:$PATH"
|
|
| 71 |
# Set PYTHONPATH so imports work correctly
|
| 72 |
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 73 |
|
|
|
|
|
|
|
|
|
|
| 74 |
# Health check
|
| 75 |
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 76 |
CMD curl -f http://localhost:8000/health || exit 1
|
| 77 |
|
| 78 |
-
#
|
| 79 |
-
|
|
|
|
|
|
|
| 80 |
ENV ENABLE_WEB_INTERFACE=true
|
| 81 |
-
CMD ["
|
|
|
|
| 59 |
|
| 60 |
WORKDIR /app
|
| 61 |
|
| 62 |
+
# Install curl (for health checks in start.sh) and streamlit
|
| 63 |
+
RUN apt-get update && \
|
| 64 |
+
apt-get install -y --no-install-recommends curl && \
|
| 65 |
+
rm -rf /var/lib/apt/lists/*
|
| 66 |
+
|
| 67 |
# Copy the virtual environment from builder
|
| 68 |
COPY --from=builder /app/env/.venv /app/.venv
|
| 69 |
|
|
|
|
| 76 |
# Set PYTHONPATH so imports work correctly
|
| 77 |
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 78 |
|
| 79 |
+
# Install streamlit into the existing venv
|
| 80 |
+
RUN pip install --no-cache-dir streamlit
|
| 81 |
+
|
| 82 |
# Health check
|
| 83 |
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
| 84 |
CMD curl -f http://localhost:8000/health || exit 1
|
| 85 |
|
| 86 |
+
# Make startup script executable
|
| 87 |
+
RUN chmod +x /app/env/start.sh
|
| 88 |
+
|
| 89 |
+
# Run both API server (background) + Streamlit UI (foreground)
|
| 90 |
ENV ENABLE_WEB_INTERFACE=true
|
| 91 |
+
CMD ["/app/env/start.sh"]
|
README.md
CHANGED
|
@@ -5,12 +5,15 @@ colorFrom: green
|
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
-
app_port:
|
| 9 |
base_path: /web
|
| 10 |
tags:
|
| 11 |
- openenv
|
| 12 |
---
|
| 13 |
|
|
|
|
|
|
|
|
|
|
| 14 |
# Jewelry Shop Manager — RL Environment
|
| 15 |
|
| 16 |
A reinforcement learning environment simulating a **jewelry shop management** pipeline. An AI agent navigates three sequential phases — buying raw materials, selecting products to craft based on demand, and negotiating sales — to maximize profit.
|
|
|
|
| 5 |
colorTo: blue
|
| 6 |
sdk: docker
|
| 7 |
pinned: false
|
| 8 |
+
app_port: 7860
|
| 9 |
base_path: /web
|
| 10 |
tags:
|
| 11 |
- openenv
|
| 12 |
---
|
| 13 |
|
| 14 |
+
Link of the environment: https://huggingface.co/spaces/hard007ik/ShopManagerEng
|
| 15 |
+
Link of Blog.md: https://huggingface.co/spaces/hard007ik/ShopManagerEng/tree/main/Blog.md
|
| 16 |
+
|
| 17 |
# Jewelry Shop Manager — RL Environment
|
| 18 |
|
| 19 |
A reinforcement learning environment simulating a **jewelry shop management** pipeline. An AI agent navigates three sequential phases — buying raw materials, selecting products to craft based on demand, and negotiating sales — to maximize profit.
|
inference.py
CHANGED
|
@@ -71,7 +71,8 @@ SYSTEM_PROMPT = textwrap.dedent(
|
|
| 71 |
Respond: "ring", "necklace", or "bracelet".
|
| 72 |
|
| 73 |
## Phase 3: SHOWROOM (negotiate)
|
| 74 |
-
|
|
|
|
| 75 |
up to 5 rounds. After 5 rounds with no acceptance, the customer leaves
|
| 76 |
(no phase-3 reward). Reject also gives 0 phase-3 reward.
|
| 77 |
Respond: "I accept" or a counter like "How about $X?". NEVER explicitly reject.
|
|
|
|
| 71 |
Respond: "ring", "necklace", or "bracelet".
|
| 72 |
|
| 73 |
## Phase 3: SHOWROOM (negotiate)
|
| 74 |
+
you makes an offer; if customer counter by telling less price from your offer, you can drop price about ~3-5% per round but make sure to not sell when loss is happening also bring max profit,
|
| 75 |
+
if customer says less price then your first told price then you have to say the price that lesser than the price you told before but more that the customer told price
|
| 76 |
up to 5 rounds. After 5 rounds with no acceptance, the customer leaves
|
| 77 |
(no phase-3 reward). Reject also gives 0 phase-3 reward.
|
| 78 |
Respond: "I accept" or a counter like "How about $X?". NEVER explicitly reject.
|
pyproject.toml
CHANGED
|
@@ -21,6 +21,7 @@ dependencies = [
|
|
| 21 |
"yfinance>=0.2.40",
|
| 22 |
"python-dotenv>=1.0.0",
|
| 23 |
"requests>=2.28.0",
|
|
|
|
| 24 |
]
|
| 25 |
|
| 26 |
[project.optional-dependencies]
|
|
@@ -29,6 +30,26 @@ dev = [
|
|
| 29 |
"pytest-cov>=4.0.0",
|
| 30 |
]
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
[project.scripts]
|
| 33 |
# Server entry point - enables running via: uv run --project . server
|
| 34 |
# or: python -m ShopManagerEng.server.app
|
|
@@ -36,5 +57,5 @@ server = "ShopManagerEng.server.app:main"
|
|
| 36 |
|
| 37 |
[tool.setuptools]
|
| 38 |
include-package-data = true
|
| 39 |
-
packages = ["ShopManagerEng", "ShopManagerEng.server"]
|
| 40 |
-
package-dir = { "ShopManagerEng" = ".", "ShopManagerEng.server" = "server" }
|
|
|
|
| 21 |
"yfinance>=0.2.40",
|
| 22 |
"python-dotenv>=1.0.0",
|
| 23 |
"requests>=2.28.0",
|
| 24 |
+
"streamlit>=1.30.0",
|
| 25 |
]
|
| 26 |
|
| 27 |
[project.optional-dependencies]
|
|
|
|
| 30 |
"pytest-cov>=4.0.0",
|
| 31 |
]
|
| 32 |
|
| 33 |
+
# GRPO training stack. Install only on a GPU host (vLLM is GPU-only).
|
| 34 |
+
# pip install -e '.[train]'
|
| 35 |
+
# Mirrors openenv-course Module 5 versions. trl>=0.17 is the cutoff that
|
| 36 |
+
# introduced trl.experimental.openenv.generate_rollout_completions.
|
| 37 |
+
train = [
|
| 38 |
+
"trl>=0.17.0",
|
| 39 |
+
"transformers>=4.46.0",
|
| 40 |
+
"datasets>=2.20.0",
|
| 41 |
+
"accelerate>=1.0.0",
|
| 42 |
+
# Pin to TRL's supported window. vllm 0.19+ changes the sampling-logprob
|
| 43 |
+
# path and breaks GRPO importance sampling (ratio collapses to ~0,
|
| 44 |
+
# grad_norm goes to 0, model stops learning). Keep <=0.18.0 until TRL
|
| 45 |
+
# bumps its supported range.
|
| 46 |
+
"vllm>=0.11.0,<=0.18.0",
|
| 47 |
+
"trackio",
|
| 48 |
+
"torch>=2.4.0",
|
| 49 |
+
# Local plotting of loss + reward curves (hackathon submission evidence).
|
| 50 |
+
"matplotlib>=3.7",
|
| 51 |
+
]
|
| 52 |
+
|
| 53 |
[project.scripts]
|
| 54 |
# Server entry point - enables running via: uv run --project . server
|
| 55 |
# or: python -m ShopManagerEng.server.app
|
|
|
|
| 57 |
|
| 58 |
[tool.setuptools]
|
| 59 |
include-package-data = true
|
| 60 |
+
packages = ["ShopManagerEng", "ShopManagerEng.server", "ShopManagerEng.training"]
|
| 61 |
+
package-dir = { "ShopManagerEng" = ".", "ShopManagerEng.server" = "server", "ShopManagerEng.training" = "training" }
|
rollout_baseline.py
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
No-TRL baseline: run random vs simple heuristic policies; log mean return.
|
| 4 |
+
Run local server first: uv run server
|
| 5 |
+
Then: SHOPMANAGER_MARKET_MODE=synthetic SHOPMANAGER_TRAIN_BASE_URL=http://127.0.0.1:8000 python rollout_baseline.py
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import argparse
|
| 10 |
+
import asyncio
|
| 11 |
+
import os
|
| 12 |
+
import random
|
| 13 |
+
import sys
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
from statistics import fmean, pstdev
|
| 16 |
+
from typing import List, Optional
|
| 17 |
+
|
| 18 |
+
ROOT = Path(__file__).resolve().parent
|
| 19 |
+
if str(ROOT) not in sys.path:
|
| 20 |
+
sys.path.insert(0, str(ROOT))
|
| 21 |
+
|
| 22 |
+
from client import JewelryShopEnv
|
| 23 |
+
from models import JewelryAction, PRODUCT_CATALOG
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _heuristic_action(obs) -> JewelryAction:
|
| 27 |
+
ph = obs.phase
|
| 28 |
+
if ph == "market":
|
| 29 |
+
g = float(obs.gold_price or 0.0) or 1.0
|
| 30 |
+
need = 1.0
|
| 31 |
+
if obs.cash >= need * g + 10:
|
| 32 |
+
return JewelryAction(
|
| 33 |
+
market_action="buy", gold_qty=need, target_price_usd=obs.gold_price
|
| 34 |
+
)
|
| 35 |
+
return JewelryAction(market_action="wait")
|
| 36 |
+
if ph == "warehouse":
|
| 37 |
+
dem = obs.demand or {"ring": 0.5, "necklace": 0.3, "bracelet": 0.2}
|
| 38 |
+
for name in sorted(dem, key=lambda k: dem.get(k, 0), reverse=True):
|
| 39 |
+
gneed = float(PRODUCT_CATALOG[name]["gold_oz"])
|
| 40 |
+
lab = float(PRODUCT_CATALOG[name]["labor"])
|
| 41 |
+
if obs.gold_oz + 1e-9 >= gneed and obs.cash + 1e-9 >= lab:
|
| 42 |
+
return JewelryAction(product_choice=name)
|
| 43 |
+
return JewelryAction(product_choice="ring")
|
| 44 |
+
if ph == "showroom":
|
| 45 |
+
if (
|
| 46 |
+
obs.current_offer
|
| 47 |
+
and obs.cost_basis > 0
|
| 48 |
+
and (float(obs.current_offer) / float(obs.cost_basis)) >= 1.15
|
| 49 |
+
) or (getattr(obs, "negotiation_round", 0) and int(obs.negotiation_round) >= 3):
|
| 50 |
+
return JewelryAction(message="I accept")
|
| 51 |
+
off = float(obs.current_offer or 0.0)
|
| 52 |
+
return JewelryAction(
|
| 53 |
+
message=f"How about ${off * 1.08:.2f}?" if off else "I need a better offer"
|
| 54 |
+
)
|
| 55 |
+
return JewelryAction()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _random_action(obs) -> JewelryAction:
|
| 59 |
+
if obs.phase == "market":
|
| 60 |
+
if random.random() < 0.35:
|
| 61 |
+
return JewelryAction(
|
| 62 |
+
market_action="buy", gold_qty=round(random.uniform(0.1, 1.2), 2)
|
| 63 |
+
)
|
| 64 |
+
return JewelryAction(market_action="wait")
|
| 65 |
+
if obs.phase == "warehouse":
|
| 66 |
+
return JewelryAction(product_choice=random.choice(["ring", "necklace", "bracelet"]))
|
| 67 |
+
return JewelryAction(
|
| 68 |
+
message=random.choice(
|
| 69 |
+
[
|
| 70 |
+
"I accept",
|
| 71 |
+
f"How about ${float(obs.current_offer or 0) * 1.1:.0f}?",
|
| 72 |
+
]
|
| 73 |
+
)
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
async def one_episode(base: str, policy: str, seed: Optional[int], max_steps: int) -> float:
|
| 78 |
+
"""
|
| 79 |
+
Run one episode under the given policy and return the trajectory return,
|
| 80 |
+
which is the env's cumulative reward (sum of per-step partials, in [0, 1]).
|
| 81 |
+
"""
|
| 82 |
+
if seed is not None:
|
| 83 |
+
random.seed(seed)
|
| 84 |
+
env = JewelryShopEnv(base_url=base)
|
| 85 |
+
r = await env.reset(seed=seed, episode_id=None)
|
| 86 |
+
o = r.observation
|
| 87 |
+
for _ in range(max_steps):
|
| 88 |
+
if r.done:
|
| 89 |
+
break
|
| 90 |
+
if policy == "heuristic":
|
| 91 |
+
a = _heuristic_action(o)
|
| 92 |
+
else:
|
| 93 |
+
a = _random_action(o)
|
| 94 |
+
r = await env.step(a)
|
| 95 |
+
o = r.observation
|
| 96 |
+
try:
|
| 97 |
+
await env.close()
|
| 98 |
+
except Exception: # noqa: BLE001
|
| 99 |
+
pass
|
| 100 |
+
# Authoritative trajectory return from the server (in [0, 1]).
|
| 101 |
+
return float(getattr(o, "cumulative_reward", 0.0))
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def main() -> None:
|
| 105 |
+
p = argparse.ArgumentParser()
|
| 106 |
+
p.add_argument("--episodes", type=int, default=20)
|
| 107 |
+
p.add_argument("--max-steps", type=int, default=25)
|
| 108 |
+
p.add_argument(
|
| 109 |
+
"--base-url",
|
| 110 |
+
default=os.environ.get("SHOPMANAGER_TRAIN_BASE_URL", "http://127.0.0.1:8000"),
|
| 111 |
+
)
|
| 112 |
+
p.add_argument("--policies", nargs="+", default=["heuristic", "random"])
|
| 113 |
+
p.add_argument("--out", type=Path, default=Path("rollout_metrics.txt"))
|
| 114 |
+
args = p.parse_args()
|
| 115 |
+
base = str(args.base_url)
|
| 116 |
+
all_lines: List[str] = [f"base_url={base}", f"episodes={args.episodes} max_steps={args.max_steps}", ""]
|
| 117 |
+
|
| 118 |
+
for name in args.policies:
|
| 119 |
+
scores: List[float] = []
|
| 120 |
+
for epi in range(args.episodes):
|
| 121 |
+
sc = asyncio.run(
|
| 122 |
+
one_episode(base, name, seed=epi, max_steps=int(args.max_steps)) # type: ignore[misc] # noqa: E501
|
| 123 |
+
)
|
| 124 |
+
scores.append(sc)
|
| 125 |
+
m = fmean(scores) if scores else 0.0
|
| 126 |
+
sd = pstdev(scores) if len(scores) > 1 else 0.0
|
| 127 |
+
line = f"{name}: mean={m:.4f} std={sd:.4f} scores={scores!s}"
|
| 128 |
+
all_lines.append(line)
|
| 129 |
+
print(line)
|
| 130 |
+
text = "\n".join(all_lines) + "\n"
|
| 131 |
+
try:
|
| 132 |
+
args.out.write_text(text, encoding="utf-8")
|
| 133 |
+
print(f"Wrote {args.out}", flush=True)
|
| 134 |
+
except OSError as err:
|
| 135 |
+
print("Could not write out file:", err, flush=True)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
if __name__ == "__main__":
|
| 139 |
+
main()
|
start.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
# Start the OpenEnv API server in the background (port 8000)
|
| 5 |
+
cd /app/env
|
| 6 |
+
uvicorn server.app:app --host 0.0.0.0 --port 8000 &
|
| 7 |
+
API_PID=$!
|
| 8 |
+
|
| 9 |
+
# Wait for the API to be ready
|
| 10 |
+
echo "[start] Waiting for API server on port 8000..."
|
| 11 |
+
for i in $(seq 1 30); do
|
| 12 |
+
if curl -sf http://localhost:8000/health > /dev/null 2>&1; then
|
| 13 |
+
echo "[start] API server ready."
|
| 14 |
+
break
|
| 15 |
+
fi
|
| 16 |
+
sleep 1
|
| 17 |
+
done
|
| 18 |
+
|
| 19 |
+
# Start Streamlit UI (port 7860 — HF Spaces default)
|
| 20 |
+
echo "[start] Launching Streamlit UI on port 7860..."
|
| 21 |
+
exec streamlit run ui.py \
|
| 22 |
+
--server.port 7860 \
|
| 23 |
+
--server.address 0.0.0.0 \
|
| 24 |
+
--server.headless true \
|
| 25 |
+
--browser.gatherUsageStats false \
|
| 26 |
+
--theme.primaryColor "#7c3aed" \
|
| 27 |
+
--theme.backgroundColor "#0f0c29" \
|
| 28 |
+
--theme.secondaryBackgroundColor "#302b63" \
|
| 29 |
+
--theme.textColor "#e2e8f0"
|
test_env_smoke.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Standalone, no-LLM, no-server smoke tests for the JewelryShop env.
|
| 2 |
+
|
| 3 |
+
Run from inside the ShopManagerEng folder so `models` / `server` import.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
cd ShopManagerEng
|
| 7 |
+
SHOPMANAGER_MARKET_MODE=synthetic python test_env_smoke.py # default: A+B
|
| 8 |
+
SHOPMANAGER_MARKET_MODE=real python test_env_smoke.py live # C only
|
| 9 |
+
python test_env_smoke.py all # all three
|
| 10 |
+
|
| 11 |
+
Why a script: putting `f'gold_price=${o.gold_price}/oz'` inside `bash -c "..."`
|
| 12 |
+
makes bash try to expand `${o.gold_price}` as a shell variable and crash with
|
| 13 |
+
`bad substitution`. Running it from a .py file removes that whole class of bugs.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
|
| 21 |
+
from server.ShopManagerEng_environment import JewelryShopEnvironment
|
| 22 |
+
from models import JewelryAction
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_reward_path() -> None:
|
| 26 |
+
"""A. Synthetic episode end-to-end, prints per-step partial + cumulative."""
|
| 27 |
+
print("\n=== A. reward path (synthetic) ===")
|
| 28 |
+
os.environ["SHOPMANAGER_MARKET_MODE"] = "synthetic"
|
| 29 |
+
e = JewelryShopEnvironment()
|
| 30 |
+
o = e.reset(seed=42, task_id="market_timing", starting_cash=10000.0)
|
| 31 |
+
print(
|
| 32 |
+
f" reset: phase={o.phase} cash=${o.cash} gold={o.gold_oz}oz "
|
| 33 |
+
f"price=${o.gold_price} weights={o.weights}"
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
actions = [
|
| 37 |
+
JewelryAction(market_action="buy", gold_qty=1.0),
|
| 38 |
+
JewelryAction(product_choice="ring"),
|
| 39 |
+
JewelryAction(message="I accept"),
|
| 40 |
+
]
|
| 41 |
+
for i, a in enumerate(actions, 1):
|
| 42 |
+
o = e.step(a)
|
| 43 |
+
print(
|
| 44 |
+
f" step {i}: phase={o.phase} reward={o.reward:.4f} "
|
| 45 |
+
f"cum={o.cumulative_reward:.4f} done={o.done}"
|
| 46 |
+
)
|
| 47 |
+
print(f" FINAL cum={o.cumulative_reward:.4f} (must be in [0, 1])")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def test_bounce_loop() -> None:
|
| 51 |
+
"""B. Warehouse cannot craft -> agent is bounced back to MARKET."""
|
| 52 |
+
print("\n=== B. bounce loop (warehouse -> market) ===")
|
| 53 |
+
os.environ["SHOPMANAGER_MARKET_MODE"] = "synthetic"
|
| 54 |
+
e = JewelryShopEnvironment()
|
| 55 |
+
o = e.reset(seed=42, task_id="profit_negotiator", starting_cash=10000.0)
|
| 56 |
+
|
| 57 |
+
o = e.step(JewelryAction(market_action="buy", gold_qty=0.2))
|
| 58 |
+
print(f" bought 0.2oz: phase={o.phase}, gold={o.gold_oz}oz")
|
| 59 |
+
|
| 60 |
+
o = e.step(JewelryAction(product_choice="ring"))
|
| 61 |
+
print(
|
| 62 |
+
f" tried ring: phase={o.phase} reentries={o.market_reentries}"
|
| 63 |
+
f"/{o.max_market_reentries} urgent={o.inventory_urgent} "
|
| 64 |
+
f"cannot_wait={o.cannot_wait}"
|
| 65 |
+
)
|
| 66 |
+
assert o.phase == "market", "expected bounce back to market"
|
| 67 |
+
assert o.inventory_urgent is True, "expected urgent flag"
|
| 68 |
+
|
| 69 |
+
o = e.step(JewelryAction(market_action="wait"))
|
| 70 |
+
print(f" tried wait while urgent: phase={o.phase} (should still be market)")
|
| 71 |
+
assert o.phase == "market", "wait should be blocked when urgent"
|
| 72 |
+
|
| 73 |
+
o = e.step(JewelryAction(market_action="buy", gold_qty=1.0))
|
| 74 |
+
print(f" bought 1.0oz more: phase={o.phase} gold={o.gold_oz}oz")
|
| 75 |
+
|
| 76 |
+
o = e.step(JewelryAction(product_choice="ring"))
|
| 77 |
+
print(f" craft ring: phase={o.phase} cum={o.cumulative_reward:.4f}")
|
| 78 |
+
|
| 79 |
+
o = e.step(JewelryAction(message="I accept"))
|
| 80 |
+
print(f" FINAL cum={o.cumulative_reward:.4f}")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def test_live_quote() -> None:
|
| 84 |
+
"""C. Real mode: live yfinance gold price (needs network)."""
|
| 85 |
+
print("\n=== C. live yfinance quote (real mode) ===")
|
| 86 |
+
os.environ["SHOPMANAGER_MARKET_MODE"] = "real"
|
| 87 |
+
e = JewelryShopEnvironment()
|
| 88 |
+
o = e.reset(seed=0, task_id="market_timing", starting_cash=10000.0)
|
| 89 |
+
print(f" gold_price=${o.gold_price}/oz source={o.gold_price_source}")
|
| 90 |
+
print(f" market_mode={o.market_mode}")
|
| 91 |
+
print(f" history(last 5)={o.gold_price_history[-5:]}")
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
def main(argv: list[str]) -> None:
|
| 95 |
+
arg = (argv[1] if len(argv) > 1 else "default").lower()
|
| 96 |
+
|
| 97 |
+
if arg in ("default", "ab"):
|
| 98 |
+
test_reward_path()
|
| 99 |
+
test_bounce_loop()
|
| 100 |
+
elif arg == "live":
|
| 101 |
+
test_live_quote()
|
| 102 |
+
elif arg == "all":
|
| 103 |
+
test_reward_path()
|
| 104 |
+
test_bounce_loop()
|
| 105 |
+
test_live_quote()
|
| 106 |
+
elif arg == "a":
|
| 107 |
+
test_reward_path()
|
| 108 |
+
elif arg == "b":
|
| 109 |
+
test_bounce_loop()
|
| 110 |
+
elif arg == "c":
|
| 111 |
+
test_live_quote()
|
| 112 |
+
else:
|
| 113 |
+
print(f"unknown arg: {arg}. use one of: a / b / c / ab / live / all")
|
| 114 |
+
sys.exit(2)
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
if __name__ == "__main__":
|
| 118 |
+
main(sys.argv)
|
train_jewelry_grpo.py
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GRPO training entry point for the JewelryShop OpenEnv (Module 5 style).
|
| 2 |
+
|
| 3 |
+
Canonical TRL >= 0.17 + OpenEnv pattern:
|
| 4 |
+
|
| 5 |
+
1. Persistent sync env client (one WebSocket reused across rollouts)
|
| 6 |
+
2. rollout_func(prompts, trainer) (returns prompt_ids/completion_ids/logprobs + rewards)
|
| 7 |
+
3. Pure reward_funcs(completions, ...) (read kwargs the rollout puts there)
|
| 8 |
+
4. GRPOTrainer(..., rollout_func=...) (NO `environment_factory`)
|
| 9 |
+
|
| 10 |
+
Design notes:
|
| 11 |
+
- Reward shaping: ONE primary reward (`reward_total = obs.cumulative_reward`)
|
| 12 |
+
drives gradients; per-phase rewards (market/warehouse/showroom) are exposed
|
| 13 |
+
for monitoring with weight 0 in the GRPO advantage.
|
| 14 |
+
- Tasks: dataset rows embed [TASK=<task_id>] which the rollout extracts so each
|
| 15 |
+
episode trains against a specific per-phase weight profile from openenv.yaml.
|
| 16 |
+
- Smoke: --smoke imports everything, builds the rollout func, opens the env
|
| 17 |
+
and does a single reset() round-trip — no GPU and no model weights needed.
|
| 18 |
+
Use this to validate wiring before paying for a GPU.
|
| 19 |
+
|
| 20 |
+
Quick local smoke (no GPU, no model load):
|
| 21 |
+
python train_jewelry_grpo.py --smoke
|
| 22 |
+
|
| 23 |
+
Full local quick check (CPU; slow, but verifies trainer.train() starts):
|
| 24 |
+
TRAIN_MODEL=Qwen/Qwen3-0.6B python train_jewelry_grpo.py --quick
|
| 25 |
+
|
| 26 |
+
Cloud (HF Jobs) — see README/TRAINING for the exact `hf jobs run` command.
|
| 27 |
+
"""
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import argparse
|
| 31 |
+
import os
|
| 32 |
+
import sys
|
| 33 |
+
from pathlib import Path
|
| 34 |
+
|
| 35 |
+
# Make `from ShopManagerEng...` imports work whether you launch this from inside
|
| 36 |
+
# the package directory or one level up.
|
| 37 |
+
ROOT = Path(__file__).resolve().parent
|
| 38 |
+
PARENT = ROOT.parent
|
| 39 |
+
if str(PARENT) not in sys.path:
|
| 40 |
+
sys.path.insert(0, str(PARENT))
|
| 41 |
+
|
| 42 |
+
try:
|
| 43 |
+
from ShopManagerEng.client import JewelryShopEnv
|
| 44 |
+
from ShopManagerEng.training.plotting import (
|
| 45 |
+
build_metrics_callback,
|
| 46 |
+
save_training_artifacts,
|
| 47 |
+
upload_training_artifacts_to_hub,
|
| 48 |
+
)
|
| 49 |
+
from ShopManagerEng.training.prompts import SYSTEM_PROMPT
|
| 50 |
+
from ShopManagerEng.training.rewards import (
|
| 51 |
+
ALL_REWARDS,
|
| 52 |
+
REWARD_WEIGHTS_MONITOR_ONLY,
|
| 53 |
+
)
|
| 54 |
+
from ShopManagerEng.training.rollout import VALID_TASKS, build_rollout_func
|
| 55 |
+
except ImportError: # script-style invocation from inside the folder
|
| 56 |
+
from client import JewelryShopEnv # type: ignore
|
| 57 |
+
from training.plotting import ( # type: ignore
|
| 58 |
+
build_metrics_callback,
|
| 59 |
+
save_training_artifacts,
|
| 60 |
+
upload_training_artifacts_to_hub,
|
| 61 |
+
)
|
| 62 |
+
from training.prompts import SYSTEM_PROMPT # type: ignore
|
| 63 |
+
from training.rewards import ( # type: ignore
|
| 64 |
+
ALL_REWARDS,
|
| 65 |
+
REWARD_WEIGHTS_MONITOR_ONLY,
|
| 66 |
+
)
|
| 67 |
+
from training.rollout import VALID_TASKS, build_rollout_func # type: ignore
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def _build_dataset(dataset_size: int):
|
| 71 |
+
"""Cycle through the 3 graded tasks; embed task id in prompt for the rollout."""
|
| 72 |
+
from datasets import Dataset
|
| 73 |
+
|
| 74 |
+
rows = []
|
| 75 |
+
for i in range(dataset_size):
|
| 76 |
+
task_id = VALID_TASKS[i % len(VALID_TASKS)]
|
| 77 |
+
rows.append(
|
| 78 |
+
{
|
| 79 |
+
"prompt": (
|
| 80 |
+
f"[TASK={task_id}] Manage a jewelry shop episode end-to-end. "
|
| 81 |
+
f"Maximize the {task_id} task reward."
|
| 82 |
+
),
|
| 83 |
+
"task_id": task_id,
|
| 84 |
+
}
|
| 85 |
+
)
|
| 86 |
+
return Dataset.from_list(rows)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def _resolve_precision():
|
| 90 |
+
"""CPU/GPU autodetect; mirror the well-tested defaults."""
|
| 91 |
+
try:
|
| 92 |
+
import torch
|
| 93 |
+
has_cuda = bool(torch.cuda.is_available())
|
| 94 |
+
except Exception:
|
| 95 |
+
has_cuda = False
|
| 96 |
+
if has_cuda:
|
| 97 |
+
return {"bf16": True}
|
| 98 |
+
return {"use_cpu": True, "bf16": False, "fp16": False}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def main() -> None:
|
| 102 |
+
ap = argparse.ArgumentParser()
|
| 103 |
+
ap.add_argument(
|
| 104 |
+
"--model",
|
| 105 |
+
default=os.environ.get("TRAIN_MODEL", "Qwen/Qwen3-1.7B"),
|
| 106 |
+
help="HF model id (default: Qwen/Qwen3-1.7B; matches openenv-course Module 5).",
|
| 107 |
+
)
|
| 108 |
+
ap.add_argument(
|
| 109 |
+
"--env-url",
|
| 110 |
+
default=os.environ.get(
|
| 111 |
+
"ENV_URL", "https://hard007ik-shopmanagereng.hf.space"
|
| 112 |
+
),
|
| 113 |
+
help="Base URL of the running OpenEnv server (Space or http://127.0.0.1:8000).",
|
| 114 |
+
)
|
| 115 |
+
ap.add_argument(
|
| 116 |
+
"--output-dir",
|
| 117 |
+
default=os.environ.get("TRAIN_OUTPUT_DIR", "shopmanager-grpo-out"),
|
| 118 |
+
)
|
| 119 |
+
ap.add_argument("--dataset-size", type=int, default=300)
|
| 120 |
+
ap.add_argument("--num-generations", type=int, default=2)
|
| 121 |
+
ap.add_argument("--per-device-batch", type=int, default=1)
|
| 122 |
+
ap.add_argument("--grad-accum", type=int, default=32)
|
| 123 |
+
ap.add_argument("--max-completion-length", type=int, default=64)
|
| 124 |
+
# NOTE: --max-prompt-length intentionally removed. Recent TRL versions
|
| 125 |
+
# (>=0.20-ish) dropped `max_prompt_length` from GRPOConfig and now infer
|
| 126 |
+
# it from the tokenizer / model context window. Passing it back errors
|
| 127 |
+
# with `TypeError: unexpected keyword argument 'max_prompt_length'`.
|
| 128 |
+
ap.add_argument("--max-turns", type=int, default=15)
|
| 129 |
+
ap.add_argument("--lr", type=float, default=5e-6)
|
| 130 |
+
ap.add_argument("--warmup-steps", type=int, default=10)
|
| 131 |
+
ap.add_argument("--max-steps", type=int, default=-1, help="-1 = epoch-bounded.")
|
| 132 |
+
ap.add_argument("--epochs", type=int, default=1)
|
| 133 |
+
ap.add_argument(
|
| 134 |
+
"--vllm-gpu-mem",
|
| 135 |
+
type=float,
|
| 136 |
+
default=0.3,
|
| 137 |
+
help="Fraction of GPU mem reserved for vLLM. Lower if OOM.",
|
| 138 |
+
)
|
| 139 |
+
ap.add_argument("--push-to-hub", action="store_true")
|
| 140 |
+
ap.add_argument(
|
| 141 |
+
"--hub-repo-id",
|
| 142 |
+
default=None,
|
| 143 |
+
help="HF model repo (user/model-name) for weight push + training-artifact upload. "
|
| 144 |
+
"If omitted, artifact upload uses {whoami}/{basename of --output-dir}.",
|
| 145 |
+
)
|
| 146 |
+
ap.add_argument(
|
| 147 |
+
"--report-to",
|
| 148 |
+
default=os.environ.get("TRAIN_REPORT_TO", "trackio"),
|
| 149 |
+
help="trackio | wandb | none",
|
| 150 |
+
)
|
| 151 |
+
ap.add_argument(
|
| 152 |
+
"--smoke",
|
| 153 |
+
action="store_true",
|
| 154 |
+
help="No training. Imports, env connect, one reset() — validates wiring.",
|
| 155 |
+
)
|
| 156 |
+
ap.add_argument(
|
| 157 |
+
"--quick",
|
| 158 |
+
action="store_true",
|
| 159 |
+
help="CPU-friendly tiny run (1 step, num_generations=2, max_completion=32).",
|
| 160 |
+
)
|
| 161 |
+
args = ap.parse_args()
|
| 162 |
+
|
| 163 |
+
if args.smoke:
|
| 164 |
+
# No transformers / vllm load: just import + connect + reset, then
|
| 165 |
+
# also exercise the plotting pipeline on a fake log_history so the
|
| 166 |
+
# submission-artifact path is proven before we burn a GPU on it.
|
| 167 |
+
env = JewelryShopEnv(base_url=args.env_url)
|
| 168 |
+
sync_env = env.sync()
|
| 169 |
+
sync_env.connect()
|
| 170 |
+
try:
|
| 171 |
+
r = sync_env.reset(task_id=VALID_TASKS[0])
|
| 172 |
+
print(f"[SMOKE] connected to {args.env_url}")
|
| 173 |
+
print(
|
| 174 |
+
f"[SMOKE] reset OK: phase={r.observation.phase}, "
|
| 175 |
+
f"done={r.done}, cumulative_reward="
|
| 176 |
+
f"{getattr(r.observation, 'cumulative_reward', 0)}"
|
| 177 |
+
)
|
| 178 |
+
print(f"[SMOKE] system prompt loaded ({len(SYSTEM_PROMPT)} chars)")
|
| 179 |
+
print("[SMOKE] reward funcs:", [f.__name__ for f in ALL_REWARDS])
|
| 180 |
+
print("[SMOKE] reward weights:", REWARD_WEIGHTS_MONITOR_ONLY)
|
| 181 |
+
finally:
|
| 182 |
+
try:
|
| 183 |
+
sync_env.close()
|
| 184 |
+
except Exception:
|
| 185 |
+
pass
|
| 186 |
+
|
| 187 |
+
# Prove the plotting pipeline works (writes PNG + CSV + JSON to
|
| 188 |
+
# output_dir using a synthetic, monotonic log). This is what the
|
| 189 |
+
# real run will produce — same code path.
|
| 190 |
+
fake_history = []
|
| 191 |
+
for step in range(1, 21):
|
| 192 |
+
fake_history.append(
|
| 193 |
+
{
|
| 194 |
+
"step": step,
|
| 195 |
+
"loss": 1.0 / step,
|
| 196 |
+
"reward": 0.05 * step,
|
| 197 |
+
"rewards/reward_total": min(0.05 * step, 1.0),
|
| 198 |
+
"rewards/reward_market": min(0.02 * step, 0.6),
|
| 199 |
+
"rewards/reward_warehouse": min(0.015 * step, 0.6),
|
| 200 |
+
"rewards/reward_showroom": min(0.018 * step, 0.6),
|
| 201 |
+
}
|
| 202 |
+
)
|
| 203 |
+
summary = save_training_artifacts(
|
| 204 |
+
fake_history,
|
| 205 |
+
args.output_dir,
|
| 206 |
+
run_config={"smoke": True, "model": args.model, "env_url": args.env_url},
|
| 207 |
+
)
|
| 208 |
+
print(
|
| 209 |
+
f"[SMOKE] plot pipeline OK -> {args.output_dir}/loss_curve.png, "
|
| 210 |
+
f"{args.output_dir}/reward_curve.png, "
|
| 211 |
+
f"{args.output_dir}/reward_total_curve.png"
|
| 212 |
+
)
|
| 213 |
+
print(
|
| 214 |
+
f"[SMOKE] summary peak_reward_total={summary['reward_total']['max']:.3f} "
|
| 215 |
+
f"final_loss={summary['loss']['final']:.4f}"
|
| 216 |
+
)
|
| 217 |
+
print("[SMOKE] OK — wiring is sane.")
|
| 218 |
+
return
|
| 219 |
+
|
| 220 |
+
# ── Heavy imports only past the smoke gate ──
|
| 221 |
+
from transformers import AutoTokenizer
|
| 222 |
+
from trl import GRPOConfig, GRPOTrainer
|
| 223 |
+
|
| 224 |
+
tokenizer = AutoTokenizer.from_pretrained(args.model)
|
| 225 |
+
if tokenizer.pad_token is None:
|
| 226 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 227 |
+
|
| 228 |
+
env = JewelryShopEnv(base_url=args.env_url)
|
| 229 |
+
sync_env = env.sync()
|
| 230 |
+
sync_env.connect()
|
| 231 |
+
print(f"[TRAIN] env: {args.env_url}")
|
| 232 |
+
print(f"[TRAIN] model: {args.model}")
|
| 233 |
+
|
| 234 |
+
rollout_func = build_rollout_func(
|
| 235 |
+
sync_env=sync_env,
|
| 236 |
+
tokenizer=tokenizer,
|
| 237 |
+
system_prompt=SYSTEM_PROMPT,
|
| 238 |
+
max_turns=args.max_turns,
|
| 239 |
+
model_name=args.model,
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
if args.quick:
|
| 243 |
+
# Override to tiny CPU-friendly numbers.
|
| 244 |
+
args.dataset_size = 4
|
| 245 |
+
args.num_generations = 2
|
| 246 |
+
args.per_device_batch = 2
|
| 247 |
+
args.grad_accum = 1
|
| 248 |
+
args.max_completion_length = 32
|
| 249 |
+
args.max_steps = 1
|
| 250 |
+
args.warmup_steps = 0
|
| 251 |
+
|
| 252 |
+
dataset = _build_dataset(args.dataset_size)
|
| 253 |
+
precision = _resolve_precision()
|
| 254 |
+
use_cpu = precision.get("use_cpu", False)
|
| 255 |
+
|
| 256 |
+
grpo_config = GRPOConfig(
|
| 257 |
+
output_dir=args.output_dir,
|
| 258 |
+
num_train_epochs=args.epochs,
|
| 259 |
+
learning_rate=args.lr,
|
| 260 |
+
gradient_accumulation_steps=args.grad_accum,
|
| 261 |
+
per_device_train_batch_size=args.per_device_batch,
|
| 262 |
+
warmup_steps=args.warmup_steps,
|
| 263 |
+
num_generations=args.num_generations,
|
| 264 |
+
max_completion_length=args.max_completion_length,
|
| 265 |
+
# `max_prompt_length` removed: not accepted by recent TRL GRPOConfig.
|
| 266 |
+
# vLLM is the canonical generation backend on GPU; turn off on CPU smoke.
|
| 267 |
+
use_vllm=not use_cpu,
|
| 268 |
+
vllm_mode="colocate" if not use_cpu else None,
|
| 269 |
+
vllm_gpu_memory_utilization=args.vllm_gpu_mem if not use_cpu else None,
|
| 270 |
+
gradient_checkpointing=True,
|
| 271 |
+
gradient_checkpointing_kwargs={"use_reentrant": False},
|
| 272 |
+
reward_weights=REWARD_WEIGHTS_MONITOR_ONLY,
|
| 273 |
+
report_to=args.report_to if args.report_to != "none" else "none",
|
| 274 |
+
logging_steps=1,
|
| 275 |
+
save_steps=20,
|
| 276 |
+
push_to_hub=args.push_to_hub,
|
| 277 |
+
max_steps=args.max_steps if args.max_steps > 0 else -1,
|
| 278 |
+
**precision,
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
print(f"[TRAIN] device={'cpu' if use_cpu else 'gpu'} precision={precision}")
|
| 282 |
+
print(f"[TRAIN] dataset_size={args.dataset_size} num_generations={args.num_generations}")
|
| 283 |
+
|
| 284 |
+
trainer = GRPOTrainer(
|
| 285 |
+
model=args.model,
|
| 286 |
+
processing_class=tokenizer,
|
| 287 |
+
reward_funcs=list(ALL_REWARDS),
|
| 288 |
+
train_dataset=dataset,
|
| 289 |
+
args=grpo_config,
|
| 290 |
+
rollout_func=rollout_func,
|
| 291 |
+
)
|
| 292 |
+
|
| 293 |
+
# Persist loss + reward plots into output_dir every N steps + at the end.
|
| 294 |
+
# This is the hackathon "evidence you actually trained" artifact set.
|
| 295 |
+
trainer.add_callback(build_metrics_callback(args.output_dir, snapshot_every=5))
|
| 296 |
+
|
| 297 |
+
run_config = {
|
| 298 |
+
"model": args.model,
|
| 299 |
+
"env_url": args.env_url,
|
| 300 |
+
"dataset_size": args.dataset_size,
|
| 301 |
+
"num_generations": args.num_generations,
|
| 302 |
+
"per_device_batch": args.per_device_batch,
|
| 303 |
+
"grad_accum": args.grad_accum,
|
| 304 |
+
"max_completion_length": args.max_completion_length,
|
| 305 |
+
"max_turns": args.max_turns,
|
| 306 |
+
"lr": args.lr,
|
| 307 |
+
"warmup_steps": args.warmup_steps,
|
| 308 |
+
"max_steps": args.max_steps,
|
| 309 |
+
"epochs": args.epochs,
|
| 310 |
+
"vllm_gpu_mem": args.vllm_gpu_mem,
|
| 311 |
+
"reward_weights": REWARD_WEIGHTS_MONITOR_ONLY,
|
| 312 |
+
"precision": precision,
|
| 313 |
+
}
|
| 314 |
+
|
| 315 |
+
try:
|
| 316 |
+
trainer.train()
|
| 317 |
+
finally:
|
| 318 |
+
try:
|
| 319 |
+
sync_env.close()
|
| 320 |
+
except Exception:
|
| 321 |
+
pass
|
| 322 |
+
# Always persist whatever metrics we have, even on a crash mid-run.
|
| 323 |
+
try:
|
| 324 |
+
summary = save_training_artifacts(
|
| 325 |
+
list(trainer.state.log_history or []),
|
| 326 |
+
args.output_dir,
|
| 327 |
+
run_config=run_config,
|
| 328 |
+
)
|
| 329 |
+
print(
|
| 330 |
+
f"[ARTIFACTS] wrote loss/reward plots + metrics to {args.output_dir}\n"
|
| 331 |
+
f"[ARTIFACTS] final loss={summary['loss']['final']:.4f} "
|
| 332 |
+
f"max_reward_total={summary['reward_total']['max']:.4f} "
|
| 333 |
+
f"final_reward_total={summary['reward_total']['final']:.4f}"
|
| 334 |
+
)
|
| 335 |
+
except Exception as exc:
|
| 336 |
+
print(f"[ARTIFACTS] failed to save metrics: {exc}")
|
| 337 |
+
|
| 338 |
+
trainer.save_model(args.output_dir)
|
| 339 |
+
if args.push_to_hub:
|
| 340 |
+
trainer.push_to_hub()
|
| 341 |
+
# Default Hub push only ships weights/tokenizer; upload plots + metrics explicitly.
|
| 342 |
+
try:
|
| 343 |
+
from huggingface_hub import whoami
|
| 344 |
+
|
| 345 |
+
user = whoami().get("name") or whoami().get("preferred_username", "user")
|
| 346 |
+
rid = (
|
| 347 |
+
args.hub_repo_id
|
| 348 |
+
or getattr(trainer.args, "hub_model_id", None)
|
| 349 |
+
or f"{user}/{Path(args.output_dir).name}"
|
| 350 |
+
)
|
| 351 |
+
uploaded = upload_training_artifacts_to_hub(args.output_dir, rid)
|
| 352 |
+
if uploaded:
|
| 353 |
+
print(
|
| 354 |
+
f"[HUB] training artifacts ({len(uploaded)} files) -> "
|
| 355 |
+
f"https://huggingface.co/{rid}/tree/main/training_artifacts"
|
| 356 |
+
)
|
| 357 |
+
except Exception as exc: # noqa: BLE001
|
| 358 |
+
print(f"[HUB] training artifact upload failed: {exc}")
|
| 359 |
+
print(f"[DONE] saved to {args.output_dir}")
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
if __name__ == "__main__":
|
| 363 |
+
main()
|
training/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""ShopManagerEng training package.
|
| 2 |
+
|
| 3 |
+
Course-style (Module 5) GRPO training scaffolding for the JewelryShop env.
|
| 4 |
+
Uses TRL's `rollout_func=...` entry point with vLLM colocate generation.
|
| 5 |
+
"""
|
training/parse_action.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Parse free-form model text into a typed JewelryAction.
|
| 2 |
+
|
| 3 |
+
Mirrors inference.py:get_action_from_text so the action surface during
|
| 4 |
+
training matches what was used during evaluation.
|
| 5 |
+
"""
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
from typing import Tuple
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
from ..models import JewelryAction
|
| 12 |
+
except ImportError:
|
| 13 |
+
from models import JewelryAction
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def parse_model_text_to_action(phase: str, text: str) -> Tuple[JewelryAction, str]:
|
| 17 |
+
"""Return (action, normalised_text) for the current phase.
|
| 18 |
+
|
| 19 |
+
Robust against typical LLM output noise: backticks, quotes, leading/trailing
|
| 20 |
+
whitespace. Falls back to safe defaults so a single bad token never breaks
|
| 21 |
+
the rollout.
|
| 22 |
+
"""
|
| 23 |
+
text = (text or "").strip().replace("`", "").strip(" \t\n\r\"'")
|
| 24 |
+
|
| 25 |
+
if phase == "market":
|
| 26 |
+
lower = text.lower()
|
| 27 |
+
if lower.startswith("buy"):
|
| 28 |
+
qty_str = lower.replace("buy", "").strip()
|
| 29 |
+
try:
|
| 30 |
+
qty = float(qty_str)
|
| 31 |
+
except ValueError:
|
| 32 |
+
qty = 1.0
|
| 33 |
+
return JewelryAction(market_action="buy", gold_qty=qty), f"buy {qty}"
|
| 34 |
+
if "wait" in lower:
|
| 35 |
+
return JewelryAction(market_action="wait"), "wait"
|
| 36 |
+
try:
|
| 37 |
+
qty = float(text)
|
| 38 |
+
return JewelryAction(market_action="buy", gold_qty=qty), f"buy {qty}"
|
| 39 |
+
except ValueError:
|
| 40 |
+
return JewelryAction(market_action="wait"), "wait"
|
| 41 |
+
|
| 42 |
+
if phase == "warehouse":
|
| 43 |
+
lower = text.lower()
|
| 44 |
+
for product in ("necklace", "bracelet", "ring"):
|
| 45 |
+
if product in lower:
|
| 46 |
+
return JewelryAction(product_choice=product), product
|
| 47 |
+
return JewelryAction(product_choice="ring"), "ring"
|
| 48 |
+
|
| 49 |
+
if phase == "showroom":
|
| 50 |
+
return JewelryAction(message=text), text
|
| 51 |
+
|
| 52 |
+
return JewelryAction(), text
|
training/plotting.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Persist training metrics + loss/reward plots to disk.
|
| 2 |
+
|
| 3 |
+
Why this exists: the hackathon submission asks for "evidence you actually
|
| 4 |
+
trained — at minimum loss and reward plots from a real run." Since we run as
|
| 5 |
+
a script (not a notebook), nothing renders automatically. This module:
|
| 6 |
+
|
| 7 |
+
* Snapshots ``trainer.state.log_history`` every N steps via a TrainerCallback
|
| 8 |
+
(so a crashed run still leaves partial evidence behind), and
|
| 9 |
+
* Dumps a final set of artifacts (CSV, JSON, PNGs) after ``trainer.train()``.
|
| 10 |
+
|
| 11 |
+
All artifacts land in the trainer's ``output_dir`` so they ride back to the
|
| 12 |
+
Hugging Face Hub when ``push_to_hub=True``.
|
| 13 |
+
"""
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import csv
|
| 17 |
+
import json
|
| 18 |
+
import logging
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Any, Dict, Iterable, List, Optional
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
# Reward keys we track. TRL logs reward functions under "rewards/<func_name>"
|
| 26 |
+
# (and a single-scalar "reward" = sum of weighted rewards).
|
| 27 |
+
PRIMARY_REWARD_KEY = "rewards/reward_total"
|
| 28 |
+
PHASE_REWARD_KEYS = (
|
| 29 |
+
"rewards/reward_market",
|
| 30 |
+
"rewards/reward_warehouse",
|
| 31 |
+
"rewards/reward_showroom",
|
| 32 |
+
)
|
| 33 |
+
LOSS_KEY = "loss"
|
| 34 |
+
STEP_KEY = "step"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _flatten_log_history(log_history: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
|
| 38 |
+
"""Make sure every row carries a `step` field even when TRL omits it on epoch logs."""
|
| 39 |
+
cleaned: List[Dict[str, Any]] = []
|
| 40 |
+
last_step = 0
|
| 41 |
+
for row in log_history:
|
| 42 |
+
step = row.get("step", row.get("global_step", last_step))
|
| 43 |
+
last_step = step or last_step
|
| 44 |
+
merged = {"step": last_step, **{k: v for k, v in row.items() if k != "step"}}
|
| 45 |
+
cleaned.append(merged)
|
| 46 |
+
return cleaned
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _series(rows: List[Dict[str, Any]], key: str) -> List[tuple]:
|
| 50 |
+
"""Return ``[(step, value), ...]`` for the given metric key."""
|
| 51 |
+
out: List[tuple] = []
|
| 52 |
+
for r in rows:
|
| 53 |
+
if key in r and r[key] is not None:
|
| 54 |
+
try:
|
| 55 |
+
out.append((int(r["step"]), float(r[key])))
|
| 56 |
+
except (TypeError, ValueError):
|
| 57 |
+
continue
|
| 58 |
+
return out
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def _save_csv(rows: List[Dict[str, Any]], path: Path) -> None:
|
| 62 |
+
if not rows:
|
| 63 |
+
return
|
| 64 |
+
columns: List[str] = []
|
| 65 |
+
seen = set()
|
| 66 |
+
for r in rows:
|
| 67 |
+
for k in r.keys():
|
| 68 |
+
if k not in seen:
|
| 69 |
+
seen.add(k)
|
| 70 |
+
columns.append(k)
|
| 71 |
+
with path.open("w", newline="") as f:
|
| 72 |
+
writer = csv.DictWriter(f, fieldnames=columns)
|
| 73 |
+
writer.writeheader()
|
| 74 |
+
writer.writerows(rows)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def _save_json(rows: List[Dict[str, Any]], path: Path) -> None:
|
| 78 |
+
with path.open("w") as f:
|
| 79 |
+
json.dump(rows, f, indent=2, default=str)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _try_plot(
|
| 83 |
+
series: Iterable[tuple],
|
| 84 |
+
title: str,
|
| 85 |
+
ylabel: str,
|
| 86 |
+
out_path: Path,
|
| 87 |
+
*,
|
| 88 |
+
label: Optional[str] = None,
|
| 89 |
+
) -> bool:
|
| 90 |
+
"""Draw a single-series line plot. Silently no-ops if matplotlib is missing."""
|
| 91 |
+
try:
|
| 92 |
+
import matplotlib
|
| 93 |
+
|
| 94 |
+
matplotlib.use("Agg")
|
| 95 |
+
import matplotlib.pyplot as plt
|
| 96 |
+
except Exception as exc:
|
| 97 |
+
logger.warning("matplotlib unavailable, skipping %s (%s)", out_path.name, exc)
|
| 98 |
+
return False
|
| 99 |
+
|
| 100 |
+
pts = list(series)
|
| 101 |
+
if not pts:
|
| 102 |
+
logger.warning("no data for %s, skipping plot", out_path.name)
|
| 103 |
+
return False
|
| 104 |
+
xs, ys = zip(*pts)
|
| 105 |
+
fig, ax = plt.subplots(figsize=(8, 4.5))
|
| 106 |
+
ax.plot(xs, ys, marker="o", linewidth=1.5, label=label or ylabel)
|
| 107 |
+
ax.set_xlabel("training step")
|
| 108 |
+
ax.set_ylabel(ylabel)
|
| 109 |
+
ax.set_title(title)
|
| 110 |
+
ax.grid(True, alpha=0.3)
|
| 111 |
+
if label:
|
| 112 |
+
ax.legend(loc="best")
|
| 113 |
+
fig.tight_layout()
|
| 114 |
+
fig.savefig(out_path, dpi=120)
|
| 115 |
+
plt.close(fig)
|
| 116 |
+
return True
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _try_plot_multi(
|
| 120 |
+
name_to_series: Dict[str, Iterable[tuple]],
|
| 121 |
+
title: str,
|
| 122 |
+
ylabel: str,
|
| 123 |
+
out_path: Path,
|
| 124 |
+
) -> bool:
|
| 125 |
+
"""Draw a multi-series line plot."""
|
| 126 |
+
try:
|
| 127 |
+
import matplotlib
|
| 128 |
+
|
| 129 |
+
matplotlib.use("Agg")
|
| 130 |
+
import matplotlib.pyplot as plt
|
| 131 |
+
except Exception as exc:
|
| 132 |
+
logger.warning("matplotlib unavailable, skipping %s (%s)", out_path.name, exc)
|
| 133 |
+
return False
|
| 134 |
+
|
| 135 |
+
fig, ax = plt.subplots(figsize=(8.5, 5))
|
| 136 |
+
drew_any = False
|
| 137 |
+
for label, pts in name_to_series.items():
|
| 138 |
+
pts = list(pts)
|
| 139 |
+
if not pts:
|
| 140 |
+
continue
|
| 141 |
+
xs, ys = zip(*pts)
|
| 142 |
+
ax.plot(xs, ys, marker="o", linewidth=1.3, label=label)
|
| 143 |
+
drew_any = True
|
| 144 |
+
if not drew_any:
|
| 145 |
+
plt.close(fig)
|
| 146 |
+
logger.warning("no data for %s, skipping plot", out_path.name)
|
| 147 |
+
return False
|
| 148 |
+
ax.set_xlabel("training step")
|
| 149 |
+
ax.set_ylabel(ylabel)
|
| 150 |
+
ax.set_title(title)
|
| 151 |
+
ax.grid(True, alpha=0.3)
|
| 152 |
+
ax.legend(loc="best")
|
| 153 |
+
fig.tight_layout()
|
| 154 |
+
fig.savefig(out_path, dpi=120)
|
| 155 |
+
plt.close(fig)
|
| 156 |
+
return True
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def _summary_stats(series: List[tuple]) -> Dict[str, float]:
|
| 160 |
+
if not series:
|
| 161 |
+
return {"final": 0.0, "max": 0.0, "min": 0.0, "mean": 0.0, "n": 0}
|
| 162 |
+
ys = [v for _, v in series]
|
| 163 |
+
return {
|
| 164 |
+
"final": float(ys[-1]),
|
| 165 |
+
"max": float(max(ys)),
|
| 166 |
+
"min": float(min(ys)),
|
| 167 |
+
"mean": float(sum(ys) / len(ys)),
|
| 168 |
+
"n": len(ys),
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def save_training_artifacts(
|
| 173 |
+
log_history: List[Dict[str, Any]],
|
| 174 |
+
output_dir: str | Path,
|
| 175 |
+
*,
|
| 176 |
+
run_config: Optional[Dict[str, Any]] = None,
|
| 177 |
+
) -> Dict[str, Any]:
|
| 178 |
+
"""Write metrics + loss/reward plots into ``output_dir``.
|
| 179 |
+
|
| 180 |
+
Returns the summary dict that was also written to ``training_summary.json``.
|
| 181 |
+
"""
|
| 182 |
+
out = Path(output_dir)
|
| 183 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 184 |
+
|
| 185 |
+
rows = _flatten_log_history(log_history)
|
| 186 |
+
_save_csv(rows, out / "metrics.csv")
|
| 187 |
+
_save_json(rows, out / "metrics.json")
|
| 188 |
+
|
| 189 |
+
loss_series = _series(rows, LOSS_KEY)
|
| 190 |
+
total_reward_series = _series(rows, PRIMARY_REWARD_KEY)
|
| 191 |
+
# Some TRL versions log a flat "reward" scalar in addition. Prefer the
|
| 192 |
+
# named primary; fall back to "reward" if the named one is empty.
|
| 193 |
+
if not total_reward_series:
|
| 194 |
+
total_reward_series = _series(rows, "reward")
|
| 195 |
+
|
| 196 |
+
phase_series = {
|
| 197 |
+
"market": _series(rows, "rewards/reward_market"),
|
| 198 |
+
"warehouse": _series(rows, "rewards/reward_warehouse"),
|
| 199 |
+
"showroom": _series(rows, "rewards/reward_showroom"),
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
_try_plot(
|
| 203 |
+
loss_series,
|
| 204 |
+
title="Training loss (GRPO)",
|
| 205 |
+
ylabel="loss",
|
| 206 |
+
out_path=out / "loss_curve.png",
|
| 207 |
+
label="loss",
|
| 208 |
+
)
|
| 209 |
+
_try_plot(
|
| 210 |
+
total_reward_series,
|
| 211 |
+
title="Reward (total) — env cumulative_reward in [0, 1]",
|
| 212 |
+
ylabel="reward",
|
| 213 |
+
out_path=out / "reward_total_curve.png",
|
| 214 |
+
label="reward_total",
|
| 215 |
+
)
|
| 216 |
+
_try_plot_multi(
|
| 217 |
+
{
|
| 218 |
+
"reward_total": total_reward_series,
|
| 219 |
+
**{f"reward_{k}": v for k, v in phase_series.items()},
|
| 220 |
+
},
|
| 221 |
+
title="Rewards over training",
|
| 222 |
+
ylabel="reward",
|
| 223 |
+
out_path=out / "reward_curve.png",
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
summary: Dict[str, Any] = {
|
| 227 |
+
"loss": _summary_stats(loss_series),
|
| 228 |
+
"reward_total": _summary_stats(total_reward_series),
|
| 229 |
+
"reward_market": _summary_stats(phase_series["market"]),
|
| 230 |
+
"reward_warehouse": _summary_stats(phase_series["warehouse"]),
|
| 231 |
+
"reward_showroom": _summary_stats(phase_series["showroom"]),
|
| 232 |
+
"n_log_rows": len(rows),
|
| 233 |
+
"output_dir": str(out.resolve()),
|
| 234 |
+
}
|
| 235 |
+
if run_config is not None:
|
| 236 |
+
summary["run_config"] = run_config
|
| 237 |
+
|
| 238 |
+
with (out / "training_summary.json").open("w") as f:
|
| 239 |
+
json.dump(summary, f, indent=2, default=str)
|
| 240 |
+
|
| 241 |
+
logger.info("Wrote training artifacts to %s", out.resolve())
|
| 242 |
+
return summary
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def build_metrics_callback(output_dir: str | Path, snapshot_every: int = 5):
|
| 246 |
+
"""Return a TrainerCallback that snapshots metrics every N steps + on end.
|
| 247 |
+
|
| 248 |
+
Imported lazily so this module can be inspected on a machine without
|
| 249 |
+
transformers installed (e.g. for the local --smoke run).
|
| 250 |
+
"""
|
| 251 |
+
from transformers.trainer_callback import TrainerCallback
|
| 252 |
+
|
| 253 |
+
out = Path(output_dir)
|
| 254 |
+
|
| 255 |
+
class MetricsSaverCallback(TrainerCallback):
|
| 256 |
+
"""Persist metrics CSV/JSON + plots periodically and at the end."""
|
| 257 |
+
|
| 258 |
+
def __init__(self) -> None:
|
| 259 |
+
self._last_snapshot_step = -1
|
| 260 |
+
|
| 261 |
+
def _snapshot(self, state) -> None:
|
| 262 |
+
try:
|
| 263 |
+
save_training_artifacts(list(state.log_history or []), out)
|
| 264 |
+
except Exception as exc: # never let plotting kill training
|
| 265 |
+
logger.warning("metrics snapshot failed: %s", exc)
|
| 266 |
+
|
| 267 |
+
def on_log(self, args, state, control, **kwargs):
|
| 268 |
+
step = int(getattr(state, "global_step", 0) or 0)
|
| 269 |
+
if step <= 0:
|
| 270 |
+
return control
|
| 271 |
+
if (step - self._last_snapshot_step) >= max(snapshot_every, 1):
|
| 272 |
+
self._snapshot(state)
|
| 273 |
+
self._last_snapshot_step = step
|
| 274 |
+
return control
|
| 275 |
+
|
| 276 |
+
def on_train_end(self, args, state, control, **kwargs):
|
| 277 |
+
self._snapshot(state)
|
| 278 |
+
return control
|
| 279 |
+
|
| 280 |
+
return MetricsSaverCallback()
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
def upload_training_artifacts_to_hub(
|
| 284 |
+
output_dir: str | Path,
|
| 285 |
+
repo_id: str,
|
| 286 |
+
*,
|
| 287 |
+
path_in_repo: str = "training_artifacts",
|
| 288 |
+
) -> list[str]:
|
| 289 |
+
"""Upload small evidence files to the same model repo (PNGs, CSV, JSON).
|
| 290 |
+
|
| 291 |
+
``GRPOTrainer.push_to_hub`` typically uploads weights/tokenizer only; this
|
| 292 |
+
adds ``metrics.csv``, ``loss_curve.png``, and related files under
|
| 293 |
+
``path_in_repo/`` on the Hub so they survive ephemeral cloud jobs.
|
| 294 |
+
"""
|
| 295 |
+
from huggingface_hub import HfApi, create_repo
|
| 296 |
+
|
| 297 |
+
out = Path(output_dir)
|
| 298 |
+
if not out.is_dir():
|
| 299 |
+
return []
|
| 300 |
+
|
| 301 |
+
create_repo(repo_id, repo_type="model", exist_ok=True)
|
| 302 |
+
api = HfApi()
|
| 303 |
+
names = (
|
| 304 |
+
"metrics.csv",
|
| 305 |
+
"metrics.json",
|
| 306 |
+
"loss_curve.png",
|
| 307 |
+
"reward_curve.png",
|
| 308 |
+
"reward_total_curve.png",
|
| 309 |
+
"training_summary.json",
|
| 310 |
+
)
|
| 311 |
+
prefix = path_in_repo.strip("/")
|
| 312 |
+
uploaded: list[str] = []
|
| 313 |
+
for name in names:
|
| 314 |
+
path = out / name
|
| 315 |
+
if not path.is_file():
|
| 316 |
+
continue
|
| 317 |
+
dest = f"{prefix}/{name}" if prefix else name
|
| 318 |
+
api.upload_file(
|
| 319 |
+
path_or_fileobj=str(path),
|
| 320 |
+
path_in_repo=dest,
|
| 321 |
+
repo_id=repo_id,
|
| 322 |
+
repo_type="model",
|
| 323 |
+
)
|
| 324 |
+
uploaded.append(dest)
|
| 325 |
+
return uploaded
|
training/prompts.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""System prompt + per-turn user-prompt builder for the JewelryShop env.
|
| 2 |
+
|
| 3 |
+
Logic mirrors `inference.py`'s `build_user_prompt` so that whatever the model
|
| 4 |
+
saw during inference evaluation it also sees during training. Kept as a plain
|
| 5 |
+
sync function (no asyncio) so it composes cleanly with TRL's rollout_func.
|
| 6 |
+
"""
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import math
|
| 10 |
+
import textwrap
|
| 11 |
+
from typing import List
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
SYSTEM_PROMPT = textwrap.dedent(
|
| 15 |
+
"""
|
| 16 |
+
You are an expert agent running a jewelry shop. The episode runs in 3 phases
|
| 17 |
+
and may loop back to MARKET if the warehouse runs out of gold. The episode
|
| 18 |
+
reward is the SUM of per-step partial rewards across the whole episode and
|
| 19 |
+
is bounded in [0, 1]. Each task weights the phases differently:
|
| 20 |
+
- market_timing -> phase 1 = 0.6, phase 2 = 0.2, phase 3 = 0.2
|
| 21 |
+
- demand_crafter -> phase 1 = 0.2, phase 2 = 0.6, phase 3 = 0.2
|
| 22 |
+
- profit_negotiator -> phase 1 = 0.2, phase 2 = 0.2, phase 3 = 0.6
|
| 23 |
+
|
| 24 |
+
## Phase 1: MARKET (buy / wait)
|
| 25 |
+
Two modes:
|
| 26 |
+
- synthetic mode: gold price moves randomly each WAIT step within a round cap.
|
| 27 |
+
- real mode: gold price comes from a live source (yfinance: GC=F),
|
| 28 |
+
no round cap; WAIT just refreshes the live quote.
|
| 29 |
+
Coordination from the warehouse:
|
| 30 |
+
- inventory_urgent=True / cannot_wait=True means you MUST buy now;
|
| 31 |
+
WAIT will be blocked. Submit "buy X.XX" with an affordable troy-oz qty.
|
| 32 |
+
Behavior:
|
| 33 |
+
- If you can wait, observe the price trend in gold_price_history before buying.
|
| 34 |
+
- Reserve cash for labor (ring=$200, necklace=$300, bracelet=$100).
|
| 35 |
+
- Respond: "buy X.XX" (troy oz of gold) or "wait".
|
| 36 |
+
|
| 37 |
+
## Phase 2: WAREHOUSE (choose product)
|
| 38 |
+
You see two demand fields:
|
| 39 |
+
- demand : the TRUE per-product demand for THIS episode (ground truth).
|
| 40 |
+
- demand_forecast : a NOISY signal you can also lean on for planning.
|
| 41 |
+
Products: ring (1oz + $200), necklace (2oz + $300), bracelet (0.5oz + $100).
|
| 42 |
+
If you don't have enough gold to craft your choice, the env may BOUNCE you back
|
| 43 |
+
to MARKET to buy more (up to max_market_reentries times). After max bounces or
|
| 44 |
+
when truly broke, the customer leaves and the episode ends.
|
| 45 |
+
Respond: "ring", "necklace", or "bracelet".
|
| 46 |
+
|
| 47 |
+
## Phase 3: SHOWROOM (negotiate)
|
| 48 |
+
The customer makes an offer; if you counter, they raise it ~5% per round,
|
| 49 |
+
up to 5 rounds. After 5 rounds with no acceptance, the customer leaves
|
| 50 |
+
(no phase-3 reward). Reject also gives 0 phase-3 reward.
|
| 51 |
+
Respond: "I accept" or a counter like "How about $X?". NEVER explicitly reject.
|
| 52 |
+
|
| 53 |
+
CRITICAL: Respond with ONLY the action value. No explanations.
|
| 54 |
+
"""
|
| 55 |
+
).strip()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def build_user_prompt(step: int, obs, last_reward: float, history: List[str]) -> str:
|
| 59 |
+
"""Format a single observation into a user prompt the LLM sees this turn.
|
| 60 |
+
|
| 61 |
+
Mirrors inference.py:build_user_prompt so the model sees the same input shape
|
| 62 |
+
during training and at evaluation time.
|
| 63 |
+
"""
|
| 64 |
+
history_block = "\n".join(history[-4:]) if history else "None"
|
| 65 |
+
|
| 66 |
+
if obs.phase == "market":
|
| 67 |
+
prices = getattr(obs, "gold_price_history", []) or []
|
| 68 |
+
trend = ""
|
| 69 |
+
if len(prices) >= 2:
|
| 70 |
+
if prices[-1] < prices[-2]:
|
| 71 |
+
trend = "FALLING (might keep dropping, consider waiting)"
|
| 72 |
+
else:
|
| 73 |
+
trend = "RISING (buy now before it gets more expensive)"
|
| 74 |
+
|
| 75 |
+
if getattr(obs, "cannot_wait", False):
|
| 76 |
+
trend = (
|
| 77 |
+
"URGENT: inventory needs gold now — you cannot wait; buy at the current "
|
| 78 |
+
"live quote with an affordable gold_qty (troy oz)."
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
max_rounds = getattr(obs, "max_market_rounds", None)
|
| 82 |
+
rounds_left = (max_rounds - getattr(obs, "market_round", 0)) if max_rounds else None
|
| 83 |
+
|
| 84 |
+
reserve = 300.0
|
| 85 |
+
gold_price = getattr(obs, "gold_price", 0) or 0
|
| 86 |
+
cash = getattr(obs, "cash", 0) or 0
|
| 87 |
+
if gold_price > 0:
|
| 88 |
+
raw_qty = (cash - reserve) / gold_price
|
| 89 |
+
suggested_qty = max(math.floor(raw_qty * 100) / 100, 0.01)
|
| 90 |
+
else:
|
| 91 |
+
suggested_qty = 1.0
|
| 92 |
+
|
| 93 |
+
rl = "unlimited" if rounds_left is None else str(rounds_left)
|
| 94 |
+
phase_hint = (
|
| 95 |
+
f"Price: ${gold_price}/oz ({getattr(obs, 'gold_price_source', '') or 'n/a'}). "
|
| 96 |
+
f"Price history: {prices}. Trend: {trend}. "
|
| 97 |
+
f"Rounds / waits so far: {getattr(obs, 'market_round', 0)}; cap: {rl}. "
|
| 98 |
+
f"Gold on hand: {getattr(obs, 'gold_oz', 0)} troy oz "
|
| 99 |
+
f"(~{getattr(obs, 'gold_grams', 0):.2f} g). "
|
| 100 |
+
f"If buying, suggested qty: {suggested_qty} oz (reserves $300 for labor). "
|
| 101 |
+
f"Respond: 'buy {suggested_qty}' or 'wait'"
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
elif obs.phase == "warehouse":
|
| 105 |
+
demand = getattr(obs, "demand", {}) or {}
|
| 106 |
+
forecast = getattr(obs, "demand_forecast", {}) or {}
|
| 107 |
+
best_product = max(demand, key=demand.get) if demand else "ring"
|
| 108 |
+
phase_hint = (
|
| 109 |
+
f"Demand (episode): ring={demand.get('ring', 0):.0%}, "
|
| 110 |
+
f"necklace={demand.get('necklace', 0):.0%}, "
|
| 111 |
+
f"bracelet={demand.get('bracelet', 0):.0%}. "
|
| 112 |
+
f"Forecast (noisy): ring={forecast.get('ring', 0):.0%}, "
|
| 113 |
+
f"necklace={forecast.get('necklace', 0):.0%}, "
|
| 114 |
+
f"bracelet={forecast.get('bracelet', 0):.0%}. "
|
| 115 |
+
f"Highest demand: {best_product}. "
|
| 116 |
+
f"You have {getattr(obs, 'gold_oz', 0)}oz gold and "
|
| 117 |
+
f"${getattr(obs, 'cash', 0)} cash. "
|
| 118 |
+
f"Respond with EXACTLY: {best_product}"
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
elif obs.phase == "showroom":
|
| 122 |
+
cost_basis = getattr(obs, "cost_basis", 0) or 0
|
| 123 |
+
current_offer = getattr(obs, "current_offer", 0) or 0
|
| 124 |
+
negotiation_round = getattr(obs, "negotiation_round", 0) or 0
|
| 125 |
+
|
| 126 |
+
margin = ""
|
| 127 |
+
if current_offer and cost_basis > 0:
|
| 128 |
+
margin_pct = ((current_offer - cost_basis) / cost_basis) * 100
|
| 129 |
+
margin = f"Margin: {margin_pct:+.1f}%. "
|
| 130 |
+
|
| 131 |
+
should_accept = negotiation_round >= 4 or (
|
| 132 |
+
current_offer and cost_basis > 0 and current_offer > cost_basis * 1.3
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
if should_accept:
|
| 136 |
+
phase_hint = (
|
| 137 |
+
f"Cost: ${cost_basis}. Offer: ${current_offer}. {margin}"
|
| 138 |
+
f"Round {negotiation_round}/5. "
|
| 139 |
+
f"Respond with EXACTLY: I accept"
|
| 140 |
+
)
|
| 141 |
+
else:
|
| 142 |
+
counter_msgs = [
|
| 143 |
+
"I need a better price for this quality piece",
|
| 144 |
+
"That's too low, this craftsmanship deserves more",
|
| 145 |
+
f"How about ${round(cost_basis * 1.4, 2)}?",
|
| 146 |
+
f"I can't go below ${round(cost_basis * 1.3, 2)}",
|
| 147 |
+
]
|
| 148 |
+
msg = counter_msgs[min(negotiation_round, len(counter_msgs) - 1)]
|
| 149 |
+
phase_hint = (
|
| 150 |
+
f"Cost: ${cost_basis}. Offer: ${current_offer}. {margin}"
|
| 151 |
+
f"Round {negotiation_round}/5. "
|
| 152 |
+
f"DO NOT ACCEPT. Counter-offer. "
|
| 153 |
+
f"Respond with EXACTLY: {msg}"
|
| 154 |
+
)
|
| 155 |
+
else:
|
| 156 |
+
phase_hint = ""
|
| 157 |
+
|
| 158 |
+
return textwrap.dedent(
|
| 159 |
+
f"""
|
| 160 |
+
Step: {step} | Phase: {obs.phase} | Last reward: {last_reward:.2f}
|
| 161 |
+
Cash: ${getattr(obs, 'cash', 0)} | Gold: {getattr(obs, 'gold_oz', 0)}oz | Rings: {getattr(obs, 'inventory', {})}
|
| 162 |
+
Gold Price: ${getattr(obs, 'gold_price', 0)}/oz
|
| 163 |
+
Env Message: {getattr(obs, 'message', '')}
|
| 164 |
+
|
| 165 |
+
{phase_hint}
|
| 166 |
+
|
| 167 |
+
History: {history_block}
|
| 168 |
+
"""
|
| 169 |
+
).strip()
|
training/rewards.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reward functions consumed by GRPOTrainer.
|
| 2 |
+
|
| 3 |
+
Design choice: ONE primary reward (``reward_total``) drives advantages, while
|
| 4 |
+
the three per-phase rewards are exposed for *monitoring only* via TRL's logged
|
| 5 |
+
reward metrics. Their config weight should be set to 0.0 to avoid double
|
| 6 |
+
counting the cumulative phase sum.
|
| 7 |
+
|
| 8 |
+
If you actually want phase-level shaping in the gradient, change the
|
| 9 |
+
GRPOConfig ``reward_weights`` to e.g. [1.0, 0.2, 0.2, 0.2].
|
| 10 |
+
"""
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
from typing import Any, List
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def _pull(kwargs: dict, key: str, n: int) -> List[float]:
|
| 17 |
+
vals = kwargs.get(key)
|
| 18 |
+
if not vals:
|
| 19 |
+
return [0.0] * n
|
| 20 |
+
return [float(v) for v in vals]
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def reward_total(completions: List[Any], **kwargs) -> List[float]:
|
| 24 |
+
"""Authoritative trajectory return: env's cumulative_reward in [0, 1]."""
|
| 25 |
+
return _pull(kwargs, "total_reward", len(completions))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def reward_market(completions: List[Any], **kwargs) -> List[float]:
|
| 29 |
+
"""Sum of per-step partials emitted while phase == 'market'. Monitoring."""
|
| 30 |
+
return _pull(kwargs, "market_reward", len(completions))
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def reward_warehouse(completions: List[Any], **kwargs) -> List[float]:
|
| 34 |
+
"""Sum of per-step partials emitted while phase == 'warehouse'. Monitoring."""
|
| 35 |
+
return _pull(kwargs, "warehouse_reward", len(completions))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def reward_showroom(completions: List[Any], **kwargs) -> List[float]:
|
| 39 |
+
"""Sum of per-step partials emitted while phase == 'showroom'. Monitoring."""
|
| 40 |
+
return _pull(kwargs, "showroom_reward", len(completions))
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# Convenience tuple for single-import use
|
| 44 |
+
ALL_REWARDS = (reward_total, reward_market, reward_warehouse, reward_showroom)
|
| 45 |
+
|
| 46 |
+
# Matching weights so only `reward_total` contributes to the GRPO advantage.
|
| 47 |
+
# Plug this straight into GRPOConfig(reward_weights=REWARD_WEIGHTS_MONITOR_ONLY).
|
| 48 |
+
REWARD_WEIGHTS_MONITOR_ONLY = [1.0, 0.0, 0.0, 0.0]
|
training/rollout.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Module 5 (TRL OpenEnv Wordle) style rollout for ShopManagerEng.
|
| 2 |
+
|
| 3 |
+
Two public symbols:
|
| 4 |
+
|
| 5 |
+
* ``rollout_once(...)`` — plays a single multi-turn jewelry-shop episode
|
| 6 |
+
against an already-connected sync env client and returns the per-episode
|
| 7 |
+
signals TRL/GRPO needs.
|
| 8 |
+
* ``build_rollout_func(...)`` — closure factory that returns the
|
| 9 |
+
``rollout_func(prompts, trainer=None)`` callable handed to ``GRPOTrainer``.
|
| 10 |
+
|
| 11 |
+
The pattern (canonical for OpenEnv + TRL >= 0.17):
|
| 12 |
+
|
| 13 |
+
sync_env = env.sync(); sync_env.connect() # one persistent WS
|
| 14 |
+
trainer = GRPOTrainer(..., rollout_func=rollout_func)
|
| 15 |
+
trainer.train()
|
| 16 |
+
"""
|
| 17 |
+
from __future__ import annotations
|
| 18 |
+
|
| 19 |
+
import re
|
| 20 |
+
from typing import Any, Callable, Dict, List, Optional
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from .parse_action import parse_model_text_to_action
|
| 24 |
+
from .prompts import build_user_prompt
|
| 25 |
+
except ImportError:
|
| 26 |
+
from training.parse_action import parse_model_text_to_action
|
| 27 |
+
from training.prompts import build_user_prompt
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# Set of valid task ids supported by openenv.yaml; first one is the default.
|
| 31 |
+
VALID_TASKS = ("market_timing", "demand_crafter", "profit_negotiator")
|
| 32 |
+
_TASK_RE = re.compile(r"\[TASK=(\w+)\]")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def extract_task_id(prompt_text: str, default: str = VALID_TASKS[0]) -> str:
|
| 36 |
+
"""Pull the [TASK=...] tag the dataset embeds, or fall back to the default."""
|
| 37 |
+
m = _TASK_RE.search(prompt_text or "")
|
| 38 |
+
if not m:
|
| 39 |
+
return default
|
| 40 |
+
candidate = m.group(1)
|
| 41 |
+
return candidate if candidate in VALID_TASKS else default
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _apply_chat_template(tokenizer, messages, model_name: str = "") -> str:
|
| 45 |
+
"""Apply chat template, opting out of Qwen3 'thinking' mode when applicable."""
|
| 46 |
+
template_kwargs: Dict[str, Any] = {
|
| 47 |
+
"add_generation_prompt": True,
|
| 48 |
+
"tokenize": False,
|
| 49 |
+
}
|
| 50 |
+
# Qwen3 family supports the `enable_thinking` switch — disable it for short
|
| 51 |
+
# action outputs. Other models silently ignore unknown kwargs in newer
|
| 52 |
+
# transformers; older ones may raise, hence the lower() guard.
|
| 53 |
+
if "qwen3" in (model_name or "").lower():
|
| 54 |
+
template_kwargs["enable_thinking"] = False
|
| 55 |
+
return tokenizer.apply_chat_template(messages, **template_kwargs)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def rollout_once(
|
| 59 |
+
*,
|
| 60 |
+
trainer,
|
| 61 |
+
sync_env,
|
| 62 |
+
tokenizer,
|
| 63 |
+
dataset_prompt: str,
|
| 64 |
+
system_prompt: str,
|
| 65 |
+
max_turns: int,
|
| 66 |
+
model_name: str = "",
|
| 67 |
+
) -> Dict[str, Any]:
|
| 68 |
+
"""Play one full jewelry-shop episode and return per-episode signals.
|
| 69 |
+
|
| 70 |
+
Returns the dict shape TRL's GRPO loop expects: ``prompt_ids``,
|
| 71 |
+
``completion_ids``, ``logprobs`` for **a single** vLLM forward (the **last
|
| 72 |
+
environment turn** in the episode) plus reward signals for reward
|
| 73 |
+
functions.
|
| 74 |
+
|
| 75 |
+
We **do not** concatenate multiple turns into one list. In ``GRPOTrainer``,
|
| 76 |
+
each batch row is ``cat(prompt_ids, completion_ids)``; vLLM's per-token
|
| 77 |
+
``logprobs`` must be for **that** exact sequence, or the importance-sampling
|
| 78 |
+
ratio (vLLM vs reference forward) collapses. Multi-turn play still runs in
|
| 79 |
+
the environment; the policy gradient is applied to the **last** action's
|
| 80 |
+
tokens, while ``total_reward`` remains the full episode return for GRPO
|
| 81 |
+
group advantages.
|
| 82 |
+
"""
|
| 83 |
+
# Late import: trl.experimental.openenv only exists for trl >= 0.17.
|
| 84 |
+
from trl.experimental.openenv import generate_rollout_completions
|
| 85 |
+
|
| 86 |
+
task_id = extract_task_id(dataset_prompt)
|
| 87 |
+
result = sync_env.reset(task_id=task_id)
|
| 88 |
+
obs = result.observation
|
| 89 |
+
|
| 90 |
+
# One (prompt_ids, completion_ids, logprobs) per vLLM call; last turn only
|
| 91 |
+
# is returned to TRL (see module docstring).
|
| 92 |
+
turn_traces: List[Dict[str, Any]] = []
|
| 93 |
+
|
| 94 |
+
history: List[str] = []
|
| 95 |
+
last_reward = 0.0
|
| 96 |
+
phase_rewards = {"market": 0.0, "warehouse": 0.0, "showroom": 0.0}
|
| 97 |
+
|
| 98 |
+
for turn in range(1, max_turns + 1):
|
| 99 |
+
if result.done:
|
| 100 |
+
break
|
| 101 |
+
|
| 102 |
+
user_prompt = build_user_prompt(turn, obs, last_reward, history)
|
| 103 |
+
messages = [
|
| 104 |
+
{"role": "system", "content": system_prompt},
|
| 105 |
+
{"role": "user", "content": user_prompt},
|
| 106 |
+
]
|
| 107 |
+
prompt_text = _apply_chat_template(tokenizer, messages, model_name=model_name)
|
| 108 |
+
|
| 109 |
+
rollout_outputs = generate_rollout_completions(trainer, [prompt_text])[0]
|
| 110 |
+
p_ids = rollout_outputs["prompt_ids"]
|
| 111 |
+
c_ids = rollout_outputs["completion_ids"]
|
| 112 |
+
lps = rollout_outputs["logprobs"]
|
| 113 |
+
p_list = p_ids.tolist() if hasattr(p_ids, "tolist") else list(p_ids)
|
| 114 |
+
c_list = c_ids.tolist() if hasattr(c_ids, "tolist") else list(c_ids)
|
| 115 |
+
turn_traces.append(
|
| 116 |
+
{
|
| 117 |
+
"prompt_ids": p_list,
|
| 118 |
+
"completion_ids": c_list,
|
| 119 |
+
"logprobs": [float(x) for x in lps],
|
| 120 |
+
}
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
completion_text = rollout_outputs.get("text") or tokenizer.decode(
|
| 124 |
+
rollout_outputs["completion_ids"], skip_special_tokens=True
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
current_phase = obs.phase
|
| 128 |
+
action, raw_action_str = parse_model_text_to_action(current_phase, completion_text)
|
| 129 |
+
|
| 130 |
+
result = sync_env.step(action)
|
| 131 |
+
obs = result.observation
|
| 132 |
+
step_reward = float(result.reward or 0.0)
|
| 133 |
+
last_reward = step_reward
|
| 134 |
+
|
| 135 |
+
if current_phase in phase_rewards:
|
| 136 |
+
phase_rewards[current_phase] += step_reward
|
| 137 |
+
|
| 138 |
+
history.append(
|
| 139 |
+
f"Step {turn} ({current_phase}): {raw_action_str!r} -> reward {step_reward:+.2f}"
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
total_reward = float(getattr(obs, "cumulative_reward", sum(phase_rewards.values())))
|
| 143 |
+
total_reward = max(0.0, min(total_reward, 1.0))
|
| 144 |
+
|
| 145 |
+
if not turn_traces:
|
| 146 |
+
raise ValueError(
|
| 147 |
+
"rollout_once produced no vLLM turns (max_turns too low or env ended "
|
| 148 |
+
"before the first action)."
|
| 149 |
+
)
|
| 150 |
+
last = turn_traces[-1]
|
| 151 |
+
|
| 152 |
+
return {
|
| 153 |
+
"prompt_ids": last["prompt_ids"],
|
| 154 |
+
"completion_ids": last["completion_ids"],
|
| 155 |
+
"logprobs": last["logprobs"],
|
| 156 |
+
"total_reward": total_reward,
|
| 157 |
+
"market_reward": float(phase_rewards["market"]),
|
| 158 |
+
"warehouse_reward": float(phase_rewards["warehouse"]),
|
| 159 |
+
"showroom_reward": float(phase_rewards["showroom"]),
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def build_rollout_func(
|
| 164 |
+
*,
|
| 165 |
+
sync_env,
|
| 166 |
+
tokenizer,
|
| 167 |
+
system_prompt: str,
|
| 168 |
+
max_turns: int = 15,
|
| 169 |
+
model_name: str = "",
|
| 170 |
+
) -> Callable[..., Dict[str, List]]:
|
| 171 |
+
"""Return ``rollout_func(prompts, trainer=None)`` closing over the env client.
|
| 172 |
+
|
| 173 |
+
A fresh episode is run for each prompt; the same persistent ``sync_env``
|
| 174 |
+
is reused across all prompts (single WebSocket session — matches Module 5).
|
| 175 |
+
"""
|
| 176 |
+
|
| 177 |
+
def rollout_func(prompts: List[str], trainer=None) -> Dict[str, List]:
|
| 178 |
+
episode_prompt_ids: List[List[int]] = []
|
| 179 |
+
episode_completion_ids: List[List[int]] = []
|
| 180 |
+
episode_logprobs: List[List[float]] = []
|
| 181 |
+
total_rewards: List[float] = []
|
| 182 |
+
market_rewards: List[float] = []
|
| 183 |
+
warehouse_rewards: List[float] = []
|
| 184 |
+
showroom_rewards: List[float] = []
|
| 185 |
+
|
| 186 |
+
for prompt_text in prompts:
|
| 187 |
+
ep = rollout_once(
|
| 188 |
+
trainer=trainer,
|
| 189 |
+
sync_env=sync_env,
|
| 190 |
+
tokenizer=tokenizer,
|
| 191 |
+
dataset_prompt=prompt_text,
|
| 192 |
+
system_prompt=system_prompt,
|
| 193 |
+
max_turns=max_turns,
|
| 194 |
+
model_name=model_name,
|
| 195 |
+
)
|
| 196 |
+
episode_prompt_ids.append(ep["prompt_ids"])
|
| 197 |
+
episode_completion_ids.append(ep["completion_ids"])
|
| 198 |
+
episode_logprobs.append(ep["logprobs"])
|
| 199 |
+
total_rewards.append(ep["total_reward"])
|
| 200 |
+
market_rewards.append(ep["market_reward"])
|
| 201 |
+
warehouse_rewards.append(ep["warehouse_reward"])
|
| 202 |
+
showroom_rewards.append(ep["showroom_reward"])
|
| 203 |
+
|
| 204 |
+
return {
|
| 205 |
+
"prompt_ids": episode_prompt_ids,
|
| 206 |
+
"completion_ids": episode_completion_ids,
|
| 207 |
+
"logprobs": episode_logprobs,
|
| 208 |
+
"total_reward": total_rewards,
|
| 209 |
+
"market_reward": market_rewards,
|
| 210 |
+
"warehouse_reward": warehouse_rewards,
|
| 211 |
+
"showroom_reward": showroom_rewards,
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
return rollout_func
|
training/training_artifacts_v1/loss_curve.png
ADDED
|
training/training_artifacts_v1/metrics.csv
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
step,loss,grad_norm,learning_rate,num_tokens,completions/mean_length,completions/min_length,completions/max_length,completions/clipped_ratio,completions/mean_terminated_length,completions/min_terminated_length,completions/max_terminated_length,rewards/reward_total/mean,rewards/reward_total/std,rewards/reward_market/mean,rewards/reward_market/std,rewards/reward_warehouse/mean,rewards/reward_warehouse/std,rewards/reward_showroom/mean,rewards/reward_showroom/std,reward,reward_std,frac_reward_zero_std,sampling/sampling_logp_difference/mean,sampling/sampling_logp_difference/max,sampling/importance_sampling_ratio/min,sampling/importance_sampling_ratio/mean,sampling/importance_sampling_ratio/max,entropy,clip_ratio/low_mean,clip_ratio/low_min,clip_ratio/high_mean,clip_ratio/high_max,clip_ratio/region_mean,step_time,epoch,train_runtime,train_samples_per_second,train_steps_per_second,total_flos,train_loss
|
| 2 |
+
1,-0.009,12.235088348388672,0.0,27758.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7793656587600708,0.12745577096939087,0.25,0.13440430164337158,0.42500001192092896,0.20160645246505737,0.10436563193798065,0.069697305560112,0.7793656587600708,0.12745577096939087,0.0,0.005470390431582928,0.22214925289154053,0.8007970452308655,0.9949374794960022,1.076386570930481,0.028439456821217846,0.0,0.0,0.0,0.0,0.0,19.228480510413647,0.05555555555555555,,,,,
|
| 3 |
+
2,0.0723,60.551937103271484,5.000000000000001e-07,55324.0,3.25,3.0,7.0,0.0,3.25,3.0,7.0,0.719434380531311,0.1505809724330902,0.30000001192092896,0.17597654461860657,0.30000001192092896,0.17597654461860657,0.11943437159061432,0.07055392116308212,0.719434380531311,0.1505809724330902,0.0,0.01085888221859932,0.28092825412750244,0.8382877111434937,1.0260276794433594,1.324359655380249,0.036137547835437545,0.0,0.0,0.0,0.0,0.0,17.92062332853675,0.1111111111111111,,,,,
|
| 4 |
+
3,0.1153,232.934814453125,1.0000000000000002e-06,83000.0,3.5,3.0,7.0,0.0,3.5,3.0,7.0,0.7885687351226807,0.1279384195804596,0.32500001788139343,0.18837162852287292,0.375,0.20160646736621857,0.08856874704360962,0.07010025531053543,0.7885687351226807,0.1279384344816208,0.0,0.0251829382032156,0.6850378513336182,0.5226751565933228,1.0358223915100098,1.9838452339172363,0.03248842835137111,0.0,0.0,0.0,0.0,0.0,17.184778176248074,0.16666666666666666,,,,,
|
| 5 |
+
4,0.0243,13.620709419250488,1.5e-06,110708.0,3.125,3.0,7.0,0.0,3.125,3.0,7.0,0.7762374877929688,0.13016164302825928,0.32499998807907104,0.18837164342403412,0.3500000238418579,0.1967477649450302,0.10123749077320099,0.06825561076402664,0.7762374877929688,0.13016162812709808,0.0,0.007013080175966024,0.2646750509738922,0.7674550414085388,0.9837819337844849,1.0260045528411865,0.03743034108288157,0.0,0.0,0.0,0.0,0.0,17.90316915512085,0.2222222222222222,,,,,
|
| 6 |
+
5,0.0004,2.0591225624084473,2.0000000000000003e-06,138452.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7784374952316284,0.12858590483665466,0.30000001192092896,0.17597654461860657,0.375,0.20160646736621857,0.10343749821186066,0.06380020827054977,0.7784374952316284,0.12858593463897705,0.0,0.001465568202547729,0.05673474818468094,0.9749767780303955,1.0017430782318115,1.058376669883728,0.008188390799773515,0.0,0.0,0.0,0.0,0.0,17.8210555203259,0.2777777777777778,,,,,
|
| 7 |
+
6,0.0915,31.611696243286133,2.5e-06,166252.0,3.125,3.0,7.0,0.0,3.125,3.0,7.0,0.7915937900543213,0.12960509955883026,0.375,0.20160646736621857,0.32500001788139343,0.18837162852287292,0.09159374982118607,0.0639258474111557,0.7915937900543213,0.12960509955883026,0.0,0.008641102351248264,0.8236088752746582,0.9935171008110046,1.0399717092514038,2.278707504272461,0.007084679029730978,0.0,0.0,0.0,0.0,0.0,18.056264080107212,0.3333333333333333,,,,,
|
| 8 |
+
7,-0.0,0.281630277633667,3e-06,193950.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7600874900817871,0.13816162943840027,0.32500001788139343,0.18837162852287292,0.32500001788139343,0.18837162852287292,0.11008749902248383,0.07028691470623016,0.7600874900817871,0.13816164433956146,0.0,3.554227441782132e-05,0.0027569520752876997,0.997247576713562,0.9999052286148071,1.0000724792480469,0.0005298306713825696,0.0,0.0,0.0,0.0,0.0,17.43799263238907,0.3888888888888889,,,,,
|
| 9 |
+
8,0.0,0.00018881642608903348,3.5e-06,221677.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7622687816619873,0.1372590959072113,0.4000000059604645,0.20320022106170654,0.25,0.13440430164337158,0.11226874589920044,0.07360353320837021,0.7622687816619873,0.1372590959072113,0.0,2.545601773817907e-07,1.5496943888138048e-06,0.9999986886978149,1.000000238418579,1.0000016689300537,4.1391018498870835e-05,0.0,0.0,0.0,0.0,0.0,17.406394600868225,0.4444444444444444,,,,,
|
| 10 |
+
9,0.0,0.00015632262511644512,4.000000000000001e-06,249549.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.775946855545044,0.137176051735878,0.375,0.20160646736621857,0.30000001192092896,0.17597654461860657,0.10094687342643738,0.058497413992881775,0.775946855545044,0.1371760368347168,0.0,2.918131087881193e-07,3.099441755693988e-06,0.9999978542327881,1.0,1.000001311302185,4.173239403826301e-05,0.0,0.0,0.0,0.0,0.0,18.514019537717104,0.5,,,,,
|
| 11 |
+
10,0.0,9.833038348006085e-05,4.5e-06,277377.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7337374687194824,0.15055809915065765,0.2750000059604645,0.1586231142282486,0.3500000238418579,0.1967477649450302,0.10873749852180481,0.06778524816036224,0.7337374687194824,0.15055811405181885,0.0,2.719489771152439e-07,1.9073031580774114e-06,0.9999985694885254,0.9999998807907104,1.0000019073486328,4.224909940830912e-05,0.0,0.0,0.0,0.0,0.0,18.26371632888913,0.5555555555555556,,,,,
|
| 12 |
+
11,-0.0,0.00015879125567153096,5e-06,305332.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7567968368530273,0.13728150725364685,0.375,0.20160646736621857,0.2750000059604645,0.1586231142282486,0.10679687559604645,0.07404065877199173,0.7567968368530273,0.13728150725364685,0.0,3.067227396513772e-07,2.6226434783893637e-06,0.9999973773956299,0.9999994039535522,1.0000019073486328,4.7876037001515215e-05,0.0,0.0,0.0,0.0,0.0,19.4472255371511,0.6111111111111112,,,,,
|
| 13 |
+
12,-0.0,0.00039661259506829083,4.3750000000000005e-06,333165.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7081500291824341,0.1421954482793808,0.30000001192092896,0.17597654461860657,0.2750000059604645,0.1586231142282486,0.13315001130104065,0.07370516657829285,0.7081500291824341,0.142195463180542,0.0,4.768499479723687e-07,5.602954843197949e-06,0.9999943971633911,0.9999988675117493,1.0000016689300537,5.9777358274004655e-05,0.0,0.0,0.0,0.0,0.0,18.45015063509345,0.6666666666666666,,,,,
|
| 14 |
+
13,0.0,0.0005282312049530447,3.7500000000000005e-06,361006.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7762781381607056,0.13823458552360535,0.32500001788139343,0.18837162852287292,0.3500000238418579,0.1967477649450302,0.10127812623977661,0.06158862262964249,0.7762781381607056,0.13823460042476654,0.0,6.830008487668238e-07,5.60290391149465e-06,0.9999943971633911,0.9999983310699463,1.0000022649765015,7.182803437899565e-05,0.0,0.0,0.0,0.0,0.0,17.955969959497452,0.7222222222222222,,,,,
|
| 15 |
+
14,-0.0,0.000535853614564985,3.125e-06,388862.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7628874778747559,0.1295449286699295,0.30000001192092896,0.17597654461860657,0.3499999940395355,0.19674775004386902,0.11288750171661377,0.073255755007267,0.7628874778747559,0.1295449435710907,0.0,1.1113726259281975e-06,2.0979605324100703e-05,0.9999922513961792,1.0000004768371582,1.0000211000442505,9.972968496185786e-05,0.0,0.0,0.0,0.0,0.0,18.99697282537818,0.7777777777777778,,,,,
|
| 16 |
+
15,0.0,0.005312301218509674,2.5e-06,416636.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7899656295776367,0.1296130269765854,0.3499999940395355,0.19674775004386902,0.3500000238418579,0.1967477649450302,0.08996562659740448,0.05463240668177605,0.7899656295776367,0.12961304187774658,0.0,4.004845322924666e-06,4.589592936099507e-05,0.9999337792396545,0.9999885559082031,1.0000029802322388,0.0001807671503684105,0.0,0.0,0.0,0.0,0.0,19.184648096561432,0.8333333333333334,,,,,
|
| 17 |
+
16,-0.0,0.005995223298668861,1.8750000000000003e-06,444544.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.789996862411499,0.12048782408237457,0.375,0.20160646736621857,0.32039374113082886,0.18316024541854858,0.09460312128067017,0.062435995787382126,0.789996862411499,0.12048781663179398,0.0,4.866625204158481e-06,2.9087463190080598e-05,0.999959409236908,0.9999855160713196,1.0000004768371582,0.00019320984370096994,0.0,0.0,0.0,0.0,0.0,19.476148523390293,0.8888888888888888,,,,,
|
| 18 |
+
17,0.0,0.011378168128430843,1.25e-06,472481.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.8168656826019287,0.09262391924858093,0.375,0.20160646736621857,0.3498094081878662,0.19690066576004028,0.09205625206232071,0.07046373933553696,0.8168656826019287,0.09262390434741974,0.0,5.950785180175444e-06,4.7328881919384e-05,0.9999284744262695,0.9999822974205017,1.000001072883606,0.00021975090658088448,0.0,0.0,0.0,0.0,0.0,19.344543006271124,0.9444444444444444,,,,,
|
| 19 |
+
18,0.0,0.018683306872844696,6.25e-07,500266.0,3.0,3.0,3.0,0.0,3.0,3.0,3.0,0.7750625014305115,0.14084643125534058,0.3500000238418579,0.1967477649450302,0.32499998807907104,0.18837164342403412,0.10006250441074371,0.05640558898448944,0.7750625014305115,0.14084644615650177,0.0625,1.1303089195280336e-05,0.00027322862297296524,0.999584436416626,0.9999662637710571,0.9999997615814209,0.000320568448614722,0.0,0.0,0.0,0.0,0.0,17.66909484937787,1.0,,,,,
|
| 20 |
+
18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1.0,406.352,0.738,0.044,0.0,0.016376476217475202
|
training/training_artifacts_v1/metrics.json
ADDED
|
@@ -0,0 +1,695 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"step": 1,
|
| 4 |
+
"loss": -0.009,
|
| 5 |
+
"grad_norm": 12.235088348388672,
|
| 6 |
+
"learning_rate": 0.0,
|
| 7 |
+
"num_tokens": 27758.0,
|
| 8 |
+
"completions/mean_length": 3.0,
|
| 9 |
+
"completions/min_length": 3.0,
|
| 10 |
+
"completions/max_length": 3.0,
|
| 11 |
+
"completions/clipped_ratio": 0.0,
|
| 12 |
+
"completions/mean_terminated_length": 3.0,
|
| 13 |
+
"completions/min_terminated_length": 3.0,
|
| 14 |
+
"completions/max_terminated_length": 3.0,
|
| 15 |
+
"rewards/reward_total/mean": 0.7793656587600708,
|
| 16 |
+
"rewards/reward_total/std": 0.12745577096939087,
|
| 17 |
+
"rewards/reward_market/mean": 0.25,
|
| 18 |
+
"rewards/reward_market/std": 0.13440430164337158,
|
| 19 |
+
"rewards/reward_warehouse/mean": 0.42500001192092896,
|
| 20 |
+
"rewards/reward_warehouse/std": 0.20160645246505737,
|
| 21 |
+
"rewards/reward_showroom/mean": 0.10436563193798065,
|
| 22 |
+
"rewards/reward_showroom/std": 0.069697305560112,
|
| 23 |
+
"reward": 0.7793656587600708,
|
| 24 |
+
"reward_std": 0.12745577096939087,
|
| 25 |
+
"frac_reward_zero_std": 0.0,
|
| 26 |
+
"sampling/sampling_logp_difference/mean": 0.005470390431582928,
|
| 27 |
+
"sampling/sampling_logp_difference/max": 0.22214925289154053,
|
| 28 |
+
"sampling/importance_sampling_ratio/min": 0.8007970452308655,
|
| 29 |
+
"sampling/importance_sampling_ratio/mean": 0.9949374794960022,
|
| 30 |
+
"sampling/importance_sampling_ratio/max": 1.076386570930481,
|
| 31 |
+
"entropy": 0.028439456821217846,
|
| 32 |
+
"clip_ratio/low_mean": 0.0,
|
| 33 |
+
"clip_ratio/low_min": 0.0,
|
| 34 |
+
"clip_ratio/high_mean": 0.0,
|
| 35 |
+
"clip_ratio/high_max": 0.0,
|
| 36 |
+
"clip_ratio/region_mean": 0.0,
|
| 37 |
+
"step_time": 19.228480510413647,
|
| 38 |
+
"epoch": 0.05555555555555555
|
| 39 |
+
},
|
| 40 |
+
{
|
| 41 |
+
"step": 2,
|
| 42 |
+
"loss": 0.0723,
|
| 43 |
+
"grad_norm": 60.551937103271484,
|
| 44 |
+
"learning_rate": 5.000000000000001e-07,
|
| 45 |
+
"num_tokens": 55324.0,
|
| 46 |
+
"completions/mean_length": 3.25,
|
| 47 |
+
"completions/min_length": 3.0,
|
| 48 |
+
"completions/max_length": 7.0,
|
| 49 |
+
"completions/clipped_ratio": 0.0,
|
| 50 |
+
"completions/mean_terminated_length": 3.25,
|
| 51 |
+
"completions/min_terminated_length": 3.0,
|
| 52 |
+
"completions/max_terminated_length": 7.0,
|
| 53 |
+
"rewards/reward_total/mean": 0.719434380531311,
|
| 54 |
+
"rewards/reward_total/std": 0.1505809724330902,
|
| 55 |
+
"rewards/reward_market/mean": 0.30000001192092896,
|
| 56 |
+
"rewards/reward_market/std": 0.17597654461860657,
|
| 57 |
+
"rewards/reward_warehouse/mean": 0.30000001192092896,
|
| 58 |
+
"rewards/reward_warehouse/std": 0.17597654461860657,
|
| 59 |
+
"rewards/reward_showroom/mean": 0.11943437159061432,
|
| 60 |
+
"rewards/reward_showroom/std": 0.07055392116308212,
|
| 61 |
+
"reward": 0.719434380531311,
|
| 62 |
+
"reward_std": 0.1505809724330902,
|
| 63 |
+
"frac_reward_zero_std": 0.0,
|
| 64 |
+
"sampling/sampling_logp_difference/mean": 0.01085888221859932,
|
| 65 |
+
"sampling/sampling_logp_difference/max": 0.28092825412750244,
|
| 66 |
+
"sampling/importance_sampling_ratio/min": 0.8382877111434937,
|
| 67 |
+
"sampling/importance_sampling_ratio/mean": 1.0260276794433594,
|
| 68 |
+
"sampling/importance_sampling_ratio/max": 1.324359655380249,
|
| 69 |
+
"entropy": 0.036137547835437545,
|
| 70 |
+
"clip_ratio/low_mean": 0.0,
|
| 71 |
+
"clip_ratio/low_min": 0.0,
|
| 72 |
+
"clip_ratio/high_mean": 0.0,
|
| 73 |
+
"clip_ratio/high_max": 0.0,
|
| 74 |
+
"clip_ratio/region_mean": 0.0,
|
| 75 |
+
"step_time": 17.92062332853675,
|
| 76 |
+
"epoch": 0.1111111111111111
|
| 77 |
+
},
|
| 78 |
+
{
|
| 79 |
+
"step": 3,
|
| 80 |
+
"loss": 0.1153,
|
| 81 |
+
"grad_norm": 232.934814453125,
|
| 82 |
+
"learning_rate": 1.0000000000000002e-06,
|
| 83 |
+
"num_tokens": 83000.0,
|
| 84 |
+
"completions/mean_length": 3.5,
|
| 85 |
+
"completions/min_length": 3.0,
|
| 86 |
+
"completions/max_length": 7.0,
|
| 87 |
+
"completions/clipped_ratio": 0.0,
|
| 88 |
+
"completions/mean_terminated_length": 3.5,
|
| 89 |
+
"completions/min_terminated_length": 3.0,
|
| 90 |
+
"completions/max_terminated_length": 7.0,
|
| 91 |
+
"rewards/reward_total/mean": 0.7885687351226807,
|
| 92 |
+
"rewards/reward_total/std": 0.1279384195804596,
|
| 93 |
+
"rewards/reward_market/mean": 0.32500001788139343,
|
| 94 |
+
"rewards/reward_market/std": 0.18837162852287292,
|
| 95 |
+
"rewards/reward_warehouse/mean": 0.375,
|
| 96 |
+
"rewards/reward_warehouse/std": 0.20160646736621857,
|
| 97 |
+
"rewards/reward_showroom/mean": 0.08856874704360962,
|
| 98 |
+
"rewards/reward_showroom/std": 0.07010025531053543,
|
| 99 |
+
"reward": 0.7885687351226807,
|
| 100 |
+
"reward_std": 0.1279384344816208,
|
| 101 |
+
"frac_reward_zero_std": 0.0,
|
| 102 |
+
"sampling/sampling_logp_difference/mean": 0.0251829382032156,
|
| 103 |
+
"sampling/sampling_logp_difference/max": 0.6850378513336182,
|
| 104 |
+
"sampling/importance_sampling_ratio/min": 0.5226751565933228,
|
| 105 |
+
"sampling/importance_sampling_ratio/mean": 1.0358223915100098,
|
| 106 |
+
"sampling/importance_sampling_ratio/max": 1.9838452339172363,
|
| 107 |
+
"entropy": 0.03248842835137111,
|
| 108 |
+
"clip_ratio/low_mean": 0.0,
|
| 109 |
+
"clip_ratio/low_min": 0.0,
|
| 110 |
+
"clip_ratio/high_mean": 0.0,
|
| 111 |
+
"clip_ratio/high_max": 0.0,
|
| 112 |
+
"clip_ratio/region_mean": 0.0,
|
| 113 |
+
"step_time": 17.184778176248074,
|
| 114 |
+
"epoch": 0.16666666666666666
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"step": 4,
|
| 118 |
+
"loss": 0.0243,
|
| 119 |
+
"grad_norm": 13.620709419250488,
|
| 120 |
+
"learning_rate": 1.5e-06,
|
| 121 |
+
"num_tokens": 110708.0,
|
| 122 |
+
"completions/mean_length": 3.125,
|
| 123 |
+
"completions/min_length": 3.0,
|
| 124 |
+
"completions/max_length": 7.0,
|
| 125 |
+
"completions/clipped_ratio": 0.0,
|
| 126 |
+
"completions/mean_terminated_length": 3.125,
|
| 127 |
+
"completions/min_terminated_length": 3.0,
|
| 128 |
+
"completions/max_terminated_length": 7.0,
|
| 129 |
+
"rewards/reward_total/mean": 0.7762374877929688,
|
| 130 |
+
"rewards/reward_total/std": 0.13016164302825928,
|
| 131 |
+
"rewards/reward_market/mean": 0.32499998807907104,
|
| 132 |
+
"rewards/reward_market/std": 0.18837164342403412,
|
| 133 |
+
"rewards/reward_warehouse/mean": 0.3500000238418579,
|
| 134 |
+
"rewards/reward_warehouse/std": 0.1967477649450302,
|
| 135 |
+
"rewards/reward_showroom/mean": 0.10123749077320099,
|
| 136 |
+
"rewards/reward_showroom/std": 0.06825561076402664,
|
| 137 |
+
"reward": 0.7762374877929688,
|
| 138 |
+
"reward_std": 0.13016162812709808,
|
| 139 |
+
"frac_reward_zero_std": 0.0,
|
| 140 |
+
"sampling/sampling_logp_difference/mean": 0.007013080175966024,
|
| 141 |
+
"sampling/sampling_logp_difference/max": 0.2646750509738922,
|
| 142 |
+
"sampling/importance_sampling_ratio/min": 0.7674550414085388,
|
| 143 |
+
"sampling/importance_sampling_ratio/mean": 0.9837819337844849,
|
| 144 |
+
"sampling/importance_sampling_ratio/max": 1.0260045528411865,
|
| 145 |
+
"entropy": 0.03743034108288157,
|
| 146 |
+
"clip_ratio/low_mean": 0.0,
|
| 147 |
+
"clip_ratio/low_min": 0.0,
|
| 148 |
+
"clip_ratio/high_mean": 0.0,
|
| 149 |
+
"clip_ratio/high_max": 0.0,
|
| 150 |
+
"clip_ratio/region_mean": 0.0,
|
| 151 |
+
"step_time": 17.90316915512085,
|
| 152 |
+
"epoch": 0.2222222222222222
|
| 153 |
+
},
|
| 154 |
+
{
|
| 155 |
+
"step": 5,
|
| 156 |
+
"loss": 0.0004,
|
| 157 |
+
"grad_norm": 2.0591225624084473,
|
| 158 |
+
"learning_rate": 2.0000000000000003e-06,
|
| 159 |
+
"num_tokens": 138452.0,
|
| 160 |
+
"completions/mean_length": 3.0,
|
| 161 |
+
"completions/min_length": 3.0,
|
| 162 |
+
"completions/max_length": 3.0,
|
| 163 |
+
"completions/clipped_ratio": 0.0,
|
| 164 |
+
"completions/mean_terminated_length": 3.0,
|
| 165 |
+
"completions/min_terminated_length": 3.0,
|
| 166 |
+
"completions/max_terminated_length": 3.0,
|
| 167 |
+
"rewards/reward_total/mean": 0.7784374952316284,
|
| 168 |
+
"rewards/reward_total/std": 0.12858590483665466,
|
| 169 |
+
"rewards/reward_market/mean": 0.30000001192092896,
|
| 170 |
+
"rewards/reward_market/std": 0.17597654461860657,
|
| 171 |
+
"rewards/reward_warehouse/mean": 0.375,
|
| 172 |
+
"rewards/reward_warehouse/std": 0.20160646736621857,
|
| 173 |
+
"rewards/reward_showroom/mean": 0.10343749821186066,
|
| 174 |
+
"rewards/reward_showroom/std": 0.06380020827054977,
|
| 175 |
+
"reward": 0.7784374952316284,
|
| 176 |
+
"reward_std": 0.12858593463897705,
|
| 177 |
+
"frac_reward_zero_std": 0.0,
|
| 178 |
+
"sampling/sampling_logp_difference/mean": 0.001465568202547729,
|
| 179 |
+
"sampling/sampling_logp_difference/max": 0.05673474818468094,
|
| 180 |
+
"sampling/importance_sampling_ratio/min": 0.9749767780303955,
|
| 181 |
+
"sampling/importance_sampling_ratio/mean": 1.0017430782318115,
|
| 182 |
+
"sampling/importance_sampling_ratio/max": 1.058376669883728,
|
| 183 |
+
"entropy": 0.008188390799773515,
|
| 184 |
+
"clip_ratio/low_mean": 0.0,
|
| 185 |
+
"clip_ratio/low_min": 0.0,
|
| 186 |
+
"clip_ratio/high_mean": 0.0,
|
| 187 |
+
"clip_ratio/high_max": 0.0,
|
| 188 |
+
"clip_ratio/region_mean": 0.0,
|
| 189 |
+
"step_time": 17.8210555203259,
|
| 190 |
+
"epoch": 0.2777777777777778
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"step": 6,
|
| 194 |
+
"loss": 0.0915,
|
| 195 |
+
"grad_norm": 31.611696243286133,
|
| 196 |
+
"learning_rate": 2.5e-06,
|
| 197 |
+
"num_tokens": 166252.0,
|
| 198 |
+
"completions/mean_length": 3.125,
|
| 199 |
+
"completions/min_length": 3.0,
|
| 200 |
+
"completions/max_length": 7.0,
|
| 201 |
+
"completions/clipped_ratio": 0.0,
|
| 202 |
+
"completions/mean_terminated_length": 3.125,
|
| 203 |
+
"completions/min_terminated_length": 3.0,
|
| 204 |
+
"completions/max_terminated_length": 7.0,
|
| 205 |
+
"rewards/reward_total/mean": 0.7915937900543213,
|
| 206 |
+
"rewards/reward_total/std": 0.12960509955883026,
|
| 207 |
+
"rewards/reward_market/mean": 0.375,
|
| 208 |
+
"rewards/reward_market/std": 0.20160646736621857,
|
| 209 |
+
"rewards/reward_warehouse/mean": 0.32500001788139343,
|
| 210 |
+
"rewards/reward_warehouse/std": 0.18837162852287292,
|
| 211 |
+
"rewards/reward_showroom/mean": 0.09159374982118607,
|
| 212 |
+
"rewards/reward_showroom/std": 0.0639258474111557,
|
| 213 |
+
"reward": 0.7915937900543213,
|
| 214 |
+
"reward_std": 0.12960509955883026,
|
| 215 |
+
"frac_reward_zero_std": 0.0,
|
| 216 |
+
"sampling/sampling_logp_difference/mean": 0.008641102351248264,
|
| 217 |
+
"sampling/sampling_logp_difference/max": 0.8236088752746582,
|
| 218 |
+
"sampling/importance_sampling_ratio/min": 0.9935171008110046,
|
| 219 |
+
"sampling/importance_sampling_ratio/mean": 1.0399717092514038,
|
| 220 |
+
"sampling/importance_sampling_ratio/max": 2.278707504272461,
|
| 221 |
+
"entropy": 0.007084679029730978,
|
| 222 |
+
"clip_ratio/low_mean": 0.0,
|
| 223 |
+
"clip_ratio/low_min": 0.0,
|
| 224 |
+
"clip_ratio/high_mean": 0.0,
|
| 225 |
+
"clip_ratio/high_max": 0.0,
|
| 226 |
+
"clip_ratio/region_mean": 0.0,
|
| 227 |
+
"step_time": 18.056264080107212,
|
| 228 |
+
"epoch": 0.3333333333333333
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"step": 7,
|
| 232 |
+
"loss": -0.0,
|
| 233 |
+
"grad_norm": 0.281630277633667,
|
| 234 |
+
"learning_rate": 3e-06,
|
| 235 |
+
"num_tokens": 193950.0,
|
| 236 |
+
"completions/mean_length": 3.0,
|
| 237 |
+
"completions/min_length": 3.0,
|
| 238 |
+
"completions/max_length": 3.0,
|
| 239 |
+
"completions/clipped_ratio": 0.0,
|
| 240 |
+
"completions/mean_terminated_length": 3.0,
|
| 241 |
+
"completions/min_terminated_length": 3.0,
|
| 242 |
+
"completions/max_terminated_length": 3.0,
|
| 243 |
+
"rewards/reward_total/mean": 0.7600874900817871,
|
| 244 |
+
"rewards/reward_total/std": 0.13816162943840027,
|
| 245 |
+
"rewards/reward_market/mean": 0.32500001788139343,
|
| 246 |
+
"rewards/reward_market/std": 0.18837162852287292,
|
| 247 |
+
"rewards/reward_warehouse/mean": 0.32500001788139343,
|
| 248 |
+
"rewards/reward_warehouse/std": 0.18837162852287292,
|
| 249 |
+
"rewards/reward_showroom/mean": 0.11008749902248383,
|
| 250 |
+
"rewards/reward_showroom/std": 0.07028691470623016,
|
| 251 |
+
"reward": 0.7600874900817871,
|
| 252 |
+
"reward_std": 0.13816164433956146,
|
| 253 |
+
"frac_reward_zero_std": 0.0,
|
| 254 |
+
"sampling/sampling_logp_difference/mean": 3.554227441782132e-05,
|
| 255 |
+
"sampling/sampling_logp_difference/max": 0.0027569520752876997,
|
| 256 |
+
"sampling/importance_sampling_ratio/min": 0.997247576713562,
|
| 257 |
+
"sampling/importance_sampling_ratio/mean": 0.9999052286148071,
|
| 258 |
+
"sampling/importance_sampling_ratio/max": 1.0000724792480469,
|
| 259 |
+
"entropy": 0.0005298306713825696,
|
| 260 |
+
"clip_ratio/low_mean": 0.0,
|
| 261 |
+
"clip_ratio/low_min": 0.0,
|
| 262 |
+
"clip_ratio/high_mean": 0.0,
|
| 263 |
+
"clip_ratio/high_max": 0.0,
|
| 264 |
+
"clip_ratio/region_mean": 0.0,
|
| 265 |
+
"step_time": 17.43799263238907,
|
| 266 |
+
"epoch": 0.3888888888888889
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"step": 8,
|
| 270 |
+
"loss": 0.0,
|
| 271 |
+
"grad_norm": 0.00018881642608903348,
|
| 272 |
+
"learning_rate": 3.5e-06,
|
| 273 |
+
"num_tokens": 221677.0,
|
| 274 |
+
"completions/mean_length": 3.0,
|
| 275 |
+
"completions/min_length": 3.0,
|
| 276 |
+
"completions/max_length": 3.0,
|
| 277 |
+
"completions/clipped_ratio": 0.0,
|
| 278 |
+
"completions/mean_terminated_length": 3.0,
|
| 279 |
+
"completions/min_terminated_length": 3.0,
|
| 280 |
+
"completions/max_terminated_length": 3.0,
|
| 281 |
+
"rewards/reward_total/mean": 0.7622687816619873,
|
| 282 |
+
"rewards/reward_total/std": 0.1372590959072113,
|
| 283 |
+
"rewards/reward_market/mean": 0.4000000059604645,
|
| 284 |
+
"rewards/reward_market/std": 0.20320022106170654,
|
| 285 |
+
"rewards/reward_warehouse/mean": 0.25,
|
| 286 |
+
"rewards/reward_warehouse/std": 0.13440430164337158,
|
| 287 |
+
"rewards/reward_showroom/mean": 0.11226874589920044,
|
| 288 |
+
"rewards/reward_showroom/std": 0.07360353320837021,
|
| 289 |
+
"reward": 0.7622687816619873,
|
| 290 |
+
"reward_std": 0.1372590959072113,
|
| 291 |
+
"frac_reward_zero_std": 0.0,
|
| 292 |
+
"sampling/sampling_logp_difference/mean": 2.545601773817907e-07,
|
| 293 |
+
"sampling/sampling_logp_difference/max": 1.5496943888138048e-06,
|
| 294 |
+
"sampling/importance_sampling_ratio/min": 0.9999986886978149,
|
| 295 |
+
"sampling/importance_sampling_ratio/mean": 1.000000238418579,
|
| 296 |
+
"sampling/importance_sampling_ratio/max": 1.0000016689300537,
|
| 297 |
+
"entropy": 4.1391018498870835e-05,
|
| 298 |
+
"clip_ratio/low_mean": 0.0,
|
| 299 |
+
"clip_ratio/low_min": 0.0,
|
| 300 |
+
"clip_ratio/high_mean": 0.0,
|
| 301 |
+
"clip_ratio/high_max": 0.0,
|
| 302 |
+
"clip_ratio/region_mean": 0.0,
|
| 303 |
+
"step_time": 17.406394600868225,
|
| 304 |
+
"epoch": 0.4444444444444444
|
| 305 |
+
},
|
| 306 |
+
{
|
| 307 |
+
"step": 9,
|
| 308 |
+
"loss": 0.0,
|
| 309 |
+
"grad_norm": 0.00015632262511644512,
|
| 310 |
+
"learning_rate": 4.000000000000001e-06,
|
| 311 |
+
"num_tokens": 249549.0,
|
| 312 |
+
"completions/mean_length": 3.0,
|
| 313 |
+
"completions/min_length": 3.0,
|
| 314 |
+
"completions/max_length": 3.0,
|
| 315 |
+
"completions/clipped_ratio": 0.0,
|
| 316 |
+
"completions/mean_terminated_length": 3.0,
|
| 317 |
+
"completions/min_terminated_length": 3.0,
|
| 318 |
+
"completions/max_terminated_length": 3.0,
|
| 319 |
+
"rewards/reward_total/mean": 0.775946855545044,
|
| 320 |
+
"rewards/reward_total/std": 0.137176051735878,
|
| 321 |
+
"rewards/reward_market/mean": 0.375,
|
| 322 |
+
"rewards/reward_market/std": 0.20160646736621857,
|
| 323 |
+
"rewards/reward_warehouse/mean": 0.30000001192092896,
|
| 324 |
+
"rewards/reward_warehouse/std": 0.17597654461860657,
|
| 325 |
+
"rewards/reward_showroom/mean": 0.10094687342643738,
|
| 326 |
+
"rewards/reward_showroom/std": 0.058497413992881775,
|
| 327 |
+
"reward": 0.775946855545044,
|
| 328 |
+
"reward_std": 0.1371760368347168,
|
| 329 |
+
"frac_reward_zero_std": 0.0,
|
| 330 |
+
"sampling/sampling_logp_difference/mean": 2.918131087881193e-07,
|
| 331 |
+
"sampling/sampling_logp_difference/max": 3.099441755693988e-06,
|
| 332 |
+
"sampling/importance_sampling_ratio/min": 0.9999978542327881,
|
| 333 |
+
"sampling/importance_sampling_ratio/mean": 1.0,
|
| 334 |
+
"sampling/importance_sampling_ratio/max": 1.000001311302185,
|
| 335 |
+
"entropy": 4.173239403826301e-05,
|
| 336 |
+
"clip_ratio/low_mean": 0.0,
|
| 337 |
+
"clip_ratio/low_min": 0.0,
|
| 338 |
+
"clip_ratio/high_mean": 0.0,
|
| 339 |
+
"clip_ratio/high_max": 0.0,
|
| 340 |
+
"clip_ratio/region_mean": 0.0,
|
| 341 |
+
"step_time": 18.514019537717104,
|
| 342 |
+
"epoch": 0.5
|
| 343 |
+
},
|
| 344 |
+
{
|
| 345 |
+
"step": 10,
|
| 346 |
+
"loss": 0.0,
|
| 347 |
+
"grad_norm": 9.833038348006085e-05,
|
| 348 |
+
"learning_rate": 4.5e-06,
|
| 349 |
+
"num_tokens": 277377.0,
|
| 350 |
+
"completions/mean_length": 3.0,
|
| 351 |
+
"completions/min_length": 3.0,
|
| 352 |
+
"completions/max_length": 3.0,
|
| 353 |
+
"completions/clipped_ratio": 0.0,
|
| 354 |
+
"completions/mean_terminated_length": 3.0,
|
| 355 |
+
"completions/min_terminated_length": 3.0,
|
| 356 |
+
"completions/max_terminated_length": 3.0,
|
| 357 |
+
"rewards/reward_total/mean": 0.7337374687194824,
|
| 358 |
+
"rewards/reward_total/std": 0.15055809915065765,
|
| 359 |
+
"rewards/reward_market/mean": 0.2750000059604645,
|
| 360 |
+
"rewards/reward_market/std": 0.1586231142282486,
|
| 361 |
+
"rewards/reward_warehouse/mean": 0.3500000238418579,
|
| 362 |
+
"rewards/reward_warehouse/std": 0.1967477649450302,
|
| 363 |
+
"rewards/reward_showroom/mean": 0.10873749852180481,
|
| 364 |
+
"rewards/reward_showroom/std": 0.06778524816036224,
|
| 365 |
+
"reward": 0.7337374687194824,
|
| 366 |
+
"reward_std": 0.15055811405181885,
|
| 367 |
+
"frac_reward_zero_std": 0.0,
|
| 368 |
+
"sampling/sampling_logp_difference/mean": 2.719489771152439e-07,
|
| 369 |
+
"sampling/sampling_logp_difference/max": 1.9073031580774114e-06,
|
| 370 |
+
"sampling/importance_sampling_ratio/min": 0.9999985694885254,
|
| 371 |
+
"sampling/importance_sampling_ratio/mean": 0.9999998807907104,
|
| 372 |
+
"sampling/importance_sampling_ratio/max": 1.0000019073486328,
|
| 373 |
+
"entropy": 4.224909940830912e-05,
|
| 374 |
+
"clip_ratio/low_mean": 0.0,
|
| 375 |
+
"clip_ratio/low_min": 0.0,
|
| 376 |
+
"clip_ratio/high_mean": 0.0,
|
| 377 |
+
"clip_ratio/high_max": 0.0,
|
| 378 |
+
"clip_ratio/region_mean": 0.0,
|
| 379 |
+
"step_time": 18.26371632888913,
|
| 380 |
+
"epoch": 0.5555555555555556
|
| 381 |
+
},
|
| 382 |
+
{
|
| 383 |
+
"step": 11,
|
| 384 |
+
"loss": -0.0,
|
| 385 |
+
"grad_norm": 0.00015879125567153096,
|
| 386 |
+
"learning_rate": 5e-06,
|
| 387 |
+
"num_tokens": 305332.0,
|
| 388 |
+
"completions/mean_length": 3.0,
|
| 389 |
+
"completions/min_length": 3.0,
|
| 390 |
+
"completions/max_length": 3.0,
|
| 391 |
+
"completions/clipped_ratio": 0.0,
|
| 392 |
+
"completions/mean_terminated_length": 3.0,
|
| 393 |
+
"completions/min_terminated_length": 3.0,
|
| 394 |
+
"completions/max_terminated_length": 3.0,
|
| 395 |
+
"rewards/reward_total/mean": 0.7567968368530273,
|
| 396 |
+
"rewards/reward_total/std": 0.13728150725364685,
|
| 397 |
+
"rewards/reward_market/mean": 0.375,
|
| 398 |
+
"rewards/reward_market/std": 0.20160646736621857,
|
| 399 |
+
"rewards/reward_warehouse/mean": 0.2750000059604645,
|
| 400 |
+
"rewards/reward_warehouse/std": 0.1586231142282486,
|
| 401 |
+
"rewards/reward_showroom/mean": 0.10679687559604645,
|
| 402 |
+
"rewards/reward_showroom/std": 0.07404065877199173,
|
| 403 |
+
"reward": 0.7567968368530273,
|
| 404 |
+
"reward_std": 0.13728150725364685,
|
| 405 |
+
"frac_reward_zero_std": 0.0,
|
| 406 |
+
"sampling/sampling_logp_difference/mean": 3.067227396513772e-07,
|
| 407 |
+
"sampling/sampling_logp_difference/max": 2.6226434783893637e-06,
|
| 408 |
+
"sampling/importance_sampling_ratio/min": 0.9999973773956299,
|
| 409 |
+
"sampling/importance_sampling_ratio/mean": 0.9999994039535522,
|
| 410 |
+
"sampling/importance_sampling_ratio/max": 1.0000019073486328,
|
| 411 |
+
"entropy": 4.7876037001515215e-05,
|
| 412 |
+
"clip_ratio/low_mean": 0.0,
|
| 413 |
+
"clip_ratio/low_min": 0.0,
|
| 414 |
+
"clip_ratio/high_mean": 0.0,
|
| 415 |
+
"clip_ratio/high_max": 0.0,
|
| 416 |
+
"clip_ratio/region_mean": 0.0,
|
| 417 |
+
"step_time": 19.4472255371511,
|
| 418 |
+
"epoch": 0.6111111111111112
|
| 419 |
+
},
|
| 420 |
+
{
|
| 421 |
+
"step": 12,
|
| 422 |
+
"loss": -0.0,
|
| 423 |
+
"grad_norm": 0.00039661259506829083,
|
| 424 |
+
"learning_rate": 4.3750000000000005e-06,
|
| 425 |
+
"num_tokens": 333165.0,
|
| 426 |
+
"completions/mean_length": 3.0,
|
| 427 |
+
"completions/min_length": 3.0,
|
| 428 |
+
"completions/max_length": 3.0,
|
| 429 |
+
"completions/clipped_ratio": 0.0,
|
| 430 |
+
"completions/mean_terminated_length": 3.0,
|
| 431 |
+
"completions/min_terminated_length": 3.0,
|
| 432 |
+
"completions/max_terminated_length": 3.0,
|
| 433 |
+
"rewards/reward_total/mean": 0.7081500291824341,
|
| 434 |
+
"rewards/reward_total/std": 0.1421954482793808,
|
| 435 |
+
"rewards/reward_market/mean": 0.30000001192092896,
|
| 436 |
+
"rewards/reward_market/std": 0.17597654461860657,
|
| 437 |
+
"rewards/reward_warehouse/mean": 0.2750000059604645,
|
| 438 |
+
"rewards/reward_warehouse/std": 0.1586231142282486,
|
| 439 |
+
"rewards/reward_showroom/mean": 0.13315001130104065,
|
| 440 |
+
"rewards/reward_showroom/std": 0.07370516657829285,
|
| 441 |
+
"reward": 0.7081500291824341,
|
| 442 |
+
"reward_std": 0.142195463180542,
|
| 443 |
+
"frac_reward_zero_std": 0.0,
|
| 444 |
+
"sampling/sampling_logp_difference/mean": 4.768499479723687e-07,
|
| 445 |
+
"sampling/sampling_logp_difference/max": 5.602954843197949e-06,
|
| 446 |
+
"sampling/importance_sampling_ratio/min": 0.9999943971633911,
|
| 447 |
+
"sampling/importance_sampling_ratio/mean": 0.9999988675117493,
|
| 448 |
+
"sampling/importance_sampling_ratio/max": 1.0000016689300537,
|
| 449 |
+
"entropy": 5.9777358274004655e-05,
|
| 450 |
+
"clip_ratio/low_mean": 0.0,
|
| 451 |
+
"clip_ratio/low_min": 0.0,
|
| 452 |
+
"clip_ratio/high_mean": 0.0,
|
| 453 |
+
"clip_ratio/high_max": 0.0,
|
| 454 |
+
"clip_ratio/region_mean": 0.0,
|
| 455 |
+
"step_time": 18.45015063509345,
|
| 456 |
+
"epoch": 0.6666666666666666
|
| 457 |
+
},
|
| 458 |
+
{
|
| 459 |
+
"step": 13,
|
| 460 |
+
"loss": 0.0,
|
| 461 |
+
"grad_norm": 0.0005282312049530447,
|
| 462 |
+
"learning_rate": 3.7500000000000005e-06,
|
| 463 |
+
"num_tokens": 361006.0,
|
| 464 |
+
"completions/mean_length": 3.0,
|
| 465 |
+
"completions/min_length": 3.0,
|
| 466 |
+
"completions/max_length": 3.0,
|
| 467 |
+
"completions/clipped_ratio": 0.0,
|
| 468 |
+
"completions/mean_terminated_length": 3.0,
|
| 469 |
+
"completions/min_terminated_length": 3.0,
|
| 470 |
+
"completions/max_terminated_length": 3.0,
|
| 471 |
+
"rewards/reward_total/mean": 0.7762781381607056,
|
| 472 |
+
"rewards/reward_total/std": 0.13823458552360535,
|
| 473 |
+
"rewards/reward_market/mean": 0.32500001788139343,
|
| 474 |
+
"rewards/reward_market/std": 0.18837162852287292,
|
| 475 |
+
"rewards/reward_warehouse/mean": 0.3500000238418579,
|
| 476 |
+
"rewards/reward_warehouse/std": 0.1967477649450302,
|
| 477 |
+
"rewards/reward_showroom/mean": 0.10127812623977661,
|
| 478 |
+
"rewards/reward_showroom/std": 0.06158862262964249,
|
| 479 |
+
"reward": 0.7762781381607056,
|
| 480 |
+
"reward_std": 0.13823460042476654,
|
| 481 |
+
"frac_reward_zero_std": 0.0,
|
| 482 |
+
"sampling/sampling_logp_difference/mean": 6.830008487668238e-07,
|
| 483 |
+
"sampling/sampling_logp_difference/max": 5.60290391149465e-06,
|
| 484 |
+
"sampling/importance_sampling_ratio/min": 0.9999943971633911,
|
| 485 |
+
"sampling/importance_sampling_ratio/mean": 0.9999983310699463,
|
| 486 |
+
"sampling/importance_sampling_ratio/max": 1.0000022649765015,
|
| 487 |
+
"entropy": 7.182803437899565e-05,
|
| 488 |
+
"clip_ratio/low_mean": 0.0,
|
| 489 |
+
"clip_ratio/low_min": 0.0,
|
| 490 |
+
"clip_ratio/high_mean": 0.0,
|
| 491 |
+
"clip_ratio/high_max": 0.0,
|
| 492 |
+
"clip_ratio/region_mean": 0.0,
|
| 493 |
+
"step_time": 17.955969959497452,
|
| 494 |
+
"epoch": 0.7222222222222222
|
| 495 |
+
},
|
| 496 |
+
{
|
| 497 |
+
"step": 14,
|
| 498 |
+
"loss": -0.0,
|
| 499 |
+
"grad_norm": 0.000535853614564985,
|
| 500 |
+
"learning_rate": 3.125e-06,
|
| 501 |
+
"num_tokens": 388862.0,
|
| 502 |
+
"completions/mean_length": 3.0,
|
| 503 |
+
"completions/min_length": 3.0,
|
| 504 |
+
"completions/max_length": 3.0,
|
| 505 |
+
"completions/clipped_ratio": 0.0,
|
| 506 |
+
"completions/mean_terminated_length": 3.0,
|
| 507 |
+
"completions/min_terminated_length": 3.0,
|
| 508 |
+
"completions/max_terminated_length": 3.0,
|
| 509 |
+
"rewards/reward_total/mean": 0.7628874778747559,
|
| 510 |
+
"rewards/reward_total/std": 0.1295449286699295,
|
| 511 |
+
"rewards/reward_market/mean": 0.30000001192092896,
|
| 512 |
+
"rewards/reward_market/std": 0.17597654461860657,
|
| 513 |
+
"rewards/reward_warehouse/mean": 0.3499999940395355,
|
| 514 |
+
"rewards/reward_warehouse/std": 0.19674775004386902,
|
| 515 |
+
"rewards/reward_showroom/mean": 0.11288750171661377,
|
| 516 |
+
"rewards/reward_showroom/std": 0.073255755007267,
|
| 517 |
+
"reward": 0.7628874778747559,
|
| 518 |
+
"reward_std": 0.1295449435710907,
|
| 519 |
+
"frac_reward_zero_std": 0.0,
|
| 520 |
+
"sampling/sampling_logp_difference/mean": 1.1113726259281975e-06,
|
| 521 |
+
"sampling/sampling_logp_difference/max": 2.0979605324100703e-05,
|
| 522 |
+
"sampling/importance_sampling_ratio/min": 0.9999922513961792,
|
| 523 |
+
"sampling/importance_sampling_ratio/mean": 1.0000004768371582,
|
| 524 |
+
"sampling/importance_sampling_ratio/max": 1.0000211000442505,
|
| 525 |
+
"entropy": 9.972968496185786e-05,
|
| 526 |
+
"clip_ratio/low_mean": 0.0,
|
| 527 |
+
"clip_ratio/low_min": 0.0,
|
| 528 |
+
"clip_ratio/high_mean": 0.0,
|
| 529 |
+
"clip_ratio/high_max": 0.0,
|
| 530 |
+
"clip_ratio/region_mean": 0.0,
|
| 531 |
+
"step_time": 18.99697282537818,
|
| 532 |
+
"epoch": 0.7777777777777778
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"step": 15,
|
| 536 |
+
"loss": 0.0,
|
| 537 |
+
"grad_norm": 0.005312301218509674,
|
| 538 |
+
"learning_rate": 2.5e-06,
|
| 539 |
+
"num_tokens": 416636.0,
|
| 540 |
+
"completions/mean_length": 3.0,
|
| 541 |
+
"completions/min_length": 3.0,
|
| 542 |
+
"completions/max_length": 3.0,
|
| 543 |
+
"completions/clipped_ratio": 0.0,
|
| 544 |
+
"completions/mean_terminated_length": 3.0,
|
| 545 |
+
"completions/min_terminated_length": 3.0,
|
| 546 |
+
"completions/max_terminated_length": 3.0,
|
| 547 |
+
"rewards/reward_total/mean": 0.7899656295776367,
|
| 548 |
+
"rewards/reward_total/std": 0.1296130269765854,
|
| 549 |
+
"rewards/reward_market/mean": 0.3499999940395355,
|
| 550 |
+
"rewards/reward_market/std": 0.19674775004386902,
|
| 551 |
+
"rewards/reward_warehouse/mean": 0.3500000238418579,
|
| 552 |
+
"rewards/reward_warehouse/std": 0.1967477649450302,
|
| 553 |
+
"rewards/reward_showroom/mean": 0.08996562659740448,
|
| 554 |
+
"rewards/reward_showroom/std": 0.05463240668177605,
|
| 555 |
+
"reward": 0.7899656295776367,
|
| 556 |
+
"reward_std": 0.12961304187774658,
|
| 557 |
+
"frac_reward_zero_std": 0.0,
|
| 558 |
+
"sampling/sampling_logp_difference/mean": 4.004845322924666e-06,
|
| 559 |
+
"sampling/sampling_logp_difference/max": 4.589592936099507e-05,
|
| 560 |
+
"sampling/importance_sampling_ratio/min": 0.9999337792396545,
|
| 561 |
+
"sampling/importance_sampling_ratio/mean": 0.9999885559082031,
|
| 562 |
+
"sampling/importance_sampling_ratio/max": 1.0000029802322388,
|
| 563 |
+
"entropy": 0.0001807671503684105,
|
| 564 |
+
"clip_ratio/low_mean": 0.0,
|
| 565 |
+
"clip_ratio/low_min": 0.0,
|
| 566 |
+
"clip_ratio/high_mean": 0.0,
|
| 567 |
+
"clip_ratio/high_max": 0.0,
|
| 568 |
+
"clip_ratio/region_mean": 0.0,
|
| 569 |
+
"step_time": 19.184648096561432,
|
| 570 |
+
"epoch": 0.8333333333333334
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"step": 16,
|
| 574 |
+
"loss": -0.0,
|
| 575 |
+
"grad_norm": 0.005995223298668861,
|
| 576 |
+
"learning_rate": 1.8750000000000003e-06,
|
| 577 |
+
"num_tokens": 444544.0,
|
| 578 |
+
"completions/mean_length": 3.0,
|
| 579 |
+
"completions/min_length": 3.0,
|
| 580 |
+
"completions/max_length": 3.0,
|
| 581 |
+
"completions/clipped_ratio": 0.0,
|
| 582 |
+
"completions/mean_terminated_length": 3.0,
|
| 583 |
+
"completions/min_terminated_length": 3.0,
|
| 584 |
+
"completions/max_terminated_length": 3.0,
|
| 585 |
+
"rewards/reward_total/mean": 0.789996862411499,
|
| 586 |
+
"rewards/reward_total/std": 0.12048782408237457,
|
| 587 |
+
"rewards/reward_market/mean": 0.375,
|
| 588 |
+
"rewards/reward_market/std": 0.20160646736621857,
|
| 589 |
+
"rewards/reward_warehouse/mean": 0.32039374113082886,
|
| 590 |
+
"rewards/reward_warehouse/std": 0.18316024541854858,
|
| 591 |
+
"rewards/reward_showroom/mean": 0.09460312128067017,
|
| 592 |
+
"rewards/reward_showroom/std": 0.062435995787382126,
|
| 593 |
+
"reward": 0.789996862411499,
|
| 594 |
+
"reward_std": 0.12048781663179398,
|
| 595 |
+
"frac_reward_zero_std": 0.0,
|
| 596 |
+
"sampling/sampling_logp_difference/mean": 4.866625204158481e-06,
|
| 597 |
+
"sampling/sampling_logp_difference/max": 2.9087463190080598e-05,
|
| 598 |
+
"sampling/importance_sampling_ratio/min": 0.999959409236908,
|
| 599 |
+
"sampling/importance_sampling_ratio/mean": 0.9999855160713196,
|
| 600 |
+
"sampling/importance_sampling_ratio/max": 1.0000004768371582,
|
| 601 |
+
"entropy": 0.00019320984370096994,
|
| 602 |
+
"clip_ratio/low_mean": 0.0,
|
| 603 |
+
"clip_ratio/low_min": 0.0,
|
| 604 |
+
"clip_ratio/high_mean": 0.0,
|
| 605 |
+
"clip_ratio/high_max": 0.0,
|
| 606 |
+
"clip_ratio/region_mean": 0.0,
|
| 607 |
+
"step_time": 19.476148523390293,
|
| 608 |
+
"epoch": 0.8888888888888888
|
| 609 |
+
},
|
| 610 |
+
{
|
| 611 |
+
"step": 17,
|
| 612 |
+
"loss": 0.0,
|
| 613 |
+
"grad_norm": 0.011378168128430843,
|
| 614 |
+
"learning_rate": 1.25e-06,
|
| 615 |
+
"num_tokens": 472481.0,
|
| 616 |
+
"completions/mean_length": 3.0,
|
| 617 |
+
"completions/min_length": 3.0,
|
| 618 |
+
"completions/max_length": 3.0,
|
| 619 |
+
"completions/clipped_ratio": 0.0,
|
| 620 |
+
"completions/mean_terminated_length": 3.0,
|
| 621 |
+
"completions/min_terminated_length": 3.0,
|
| 622 |
+
"completions/max_terminated_length": 3.0,
|
| 623 |
+
"rewards/reward_total/mean": 0.8168656826019287,
|
| 624 |
+
"rewards/reward_total/std": 0.09262391924858093,
|
| 625 |
+
"rewards/reward_market/mean": 0.375,
|
| 626 |
+
"rewards/reward_market/std": 0.20160646736621857,
|
| 627 |
+
"rewards/reward_warehouse/mean": 0.3498094081878662,
|
| 628 |
+
"rewards/reward_warehouse/std": 0.19690066576004028,
|
| 629 |
+
"rewards/reward_showroom/mean": 0.09205625206232071,
|
| 630 |
+
"rewards/reward_showroom/std": 0.07046373933553696,
|
| 631 |
+
"reward": 0.8168656826019287,
|
| 632 |
+
"reward_std": 0.09262390434741974,
|
| 633 |
+
"frac_reward_zero_std": 0.0,
|
| 634 |
+
"sampling/sampling_logp_difference/mean": 5.950785180175444e-06,
|
| 635 |
+
"sampling/sampling_logp_difference/max": 4.7328881919384e-05,
|
| 636 |
+
"sampling/importance_sampling_ratio/min": 0.9999284744262695,
|
| 637 |
+
"sampling/importance_sampling_ratio/mean": 0.9999822974205017,
|
| 638 |
+
"sampling/importance_sampling_ratio/max": 1.000001072883606,
|
| 639 |
+
"entropy": 0.00021975090658088448,
|
| 640 |
+
"clip_ratio/low_mean": 0.0,
|
| 641 |
+
"clip_ratio/low_min": 0.0,
|
| 642 |
+
"clip_ratio/high_mean": 0.0,
|
| 643 |
+
"clip_ratio/high_max": 0.0,
|
| 644 |
+
"clip_ratio/region_mean": 0.0,
|
| 645 |
+
"step_time": 19.344543006271124,
|
| 646 |
+
"epoch": 0.9444444444444444
|
| 647 |
+
},
|
| 648 |
+
{
|
| 649 |
+
"step": 18,
|
| 650 |
+
"loss": 0.0,
|
| 651 |
+
"grad_norm": 0.018683306872844696,
|
| 652 |
+
"learning_rate": 6.25e-07,
|
| 653 |
+
"num_tokens": 500266.0,
|
| 654 |
+
"completions/mean_length": 3.0,
|
| 655 |
+
"completions/min_length": 3.0,
|
| 656 |
+
"completions/max_length": 3.0,
|
| 657 |
+
"completions/clipped_ratio": 0.0,
|
| 658 |
+
"completions/mean_terminated_length": 3.0,
|
| 659 |
+
"completions/min_terminated_length": 3.0,
|
| 660 |
+
"completions/max_terminated_length": 3.0,
|
| 661 |
+
"rewards/reward_total/mean": 0.7750625014305115,
|
| 662 |
+
"rewards/reward_total/std": 0.14084643125534058,
|
| 663 |
+
"rewards/reward_market/mean": 0.3500000238418579,
|
| 664 |
+
"rewards/reward_market/std": 0.1967477649450302,
|
| 665 |
+
"rewards/reward_warehouse/mean": 0.32499998807907104,
|
| 666 |
+
"rewards/reward_warehouse/std": 0.18837164342403412,
|
| 667 |
+
"rewards/reward_showroom/mean": 0.10006250441074371,
|
| 668 |
+
"rewards/reward_showroom/std": 0.05640558898448944,
|
| 669 |
+
"reward": 0.7750625014305115,
|
| 670 |
+
"reward_std": 0.14084644615650177,
|
| 671 |
+
"frac_reward_zero_std": 0.0625,
|
| 672 |
+
"sampling/sampling_logp_difference/mean": 1.1303089195280336e-05,
|
| 673 |
+
"sampling/sampling_logp_difference/max": 0.00027322862297296524,
|
| 674 |
+
"sampling/importance_sampling_ratio/min": 0.999584436416626,
|
| 675 |
+
"sampling/importance_sampling_ratio/mean": 0.9999662637710571,
|
| 676 |
+
"sampling/importance_sampling_ratio/max": 0.9999997615814209,
|
| 677 |
+
"entropy": 0.000320568448614722,
|
| 678 |
+
"clip_ratio/low_mean": 0.0,
|
| 679 |
+
"clip_ratio/low_min": 0.0,
|
| 680 |
+
"clip_ratio/high_mean": 0.0,
|
| 681 |
+
"clip_ratio/high_max": 0.0,
|
| 682 |
+
"clip_ratio/region_mean": 0.0,
|
| 683 |
+
"step_time": 17.66909484937787,
|
| 684 |
+
"epoch": 1.0
|
| 685 |
+
},
|
| 686 |
+
{
|
| 687 |
+
"step": 18,
|
| 688 |
+
"train_runtime": 406.352,
|
| 689 |
+
"train_samples_per_second": 0.738,
|
| 690 |
+
"train_steps_per_second": 0.044,
|
| 691 |
+
"total_flos": 0.0,
|
| 692 |
+
"train_loss": 0.016376476217475202,
|
| 693 |
+
"epoch": 1.0
|
| 694 |
+
}
|
| 695 |
+
]
|
training/training_artifacts_v1/reward_curve.png
ADDED
|
training/training_artifacts_v1/reward_total_curve.png
ADDED
|
training/training_artifacts_v1/training_summary.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"loss": {
|
| 3 |
+
"final": 0.0,
|
| 4 |
+
"max": 0.1153,
|
| 5 |
+
"min": -0.009,
|
| 6 |
+
"mean": 0.01637777777777778,
|
| 7 |
+
"n": 18
|
| 8 |
+
},
|
| 9 |
+
"reward_total": {
|
| 10 |
+
"final": 0.7750625014305115,
|
| 11 |
+
"max": 0.8168656826019287,
|
| 12 |
+
"min": 0.7081500291824341,
|
| 13 |
+
"mean": 0.7689822945329878,
|
| 14 |
+
"n": 18
|
| 15 |
+
},
|
| 16 |
+
"reward_market": {
|
| 17 |
+
"final": 0.0,
|
| 18 |
+
"max": 0.0,
|
| 19 |
+
"min": 0.0,
|
| 20 |
+
"mean": 0.0,
|
| 21 |
+
"n": 0
|
| 22 |
+
},
|
| 23 |
+
"reward_warehouse": {
|
| 24 |
+
"final": 0.0,
|
| 25 |
+
"max": 0.0,
|
| 26 |
+
"min": 0.0,
|
| 27 |
+
"mean": 0.0,
|
| 28 |
+
"n": 0
|
| 29 |
+
},
|
| 30 |
+
"reward_showroom": {
|
| 31 |
+
"final": 0.0,
|
| 32 |
+
"max": 0.0,
|
| 33 |
+
"min": 0.0,
|
| 34 |
+
"mean": 0.0,
|
| 35 |
+
"n": 0
|
| 36 |
+
},
|
| 37 |
+
"n_log_rows": 19,
|
| 38 |
+
"output_dir": "/workspace/shopmanager-grpo-qwen3",
|
| 39 |
+
"run_config": {
|
| 40 |
+
"model": "Qwen/Qwen3-1.7B",
|
| 41 |
+
"env_url": "https://hard007ik-shopmanagereng.hf.space",
|
| 42 |
+
"dataset_size": 300,
|
| 43 |
+
"num_generations": 2,
|
| 44 |
+
"per_device_batch": 1,
|
| 45 |
+
"grad_accum": 32,
|
| 46 |
+
"max_completion_length": 64,
|
| 47 |
+
"max_turns": 15,
|
| 48 |
+
"lr": 5e-06,
|
| 49 |
+
"warmup_steps": 10,
|
| 50 |
+
"max_steps": -1,
|
| 51 |
+
"epochs": 1,
|
| 52 |
+
"vllm_gpu_mem": 0.3,
|
| 53 |
+
"reward_weights": [
|
| 54 |
+
1.0,
|
| 55 |
+
0.0,
|
| 56 |
+
0.0,
|
| 57 |
+
0.0
|
| 58 |
+
],
|
| 59 |
+
"precision": {
|
| 60 |
+
"bf16": true
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
ui.py
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Streamlit UI for ShopManagerEng — Interactive Jewelry Shop Demo.
|
| 3 |
+
|
| 4 |
+
An AI heuristic agent automatically plays through each episode.
|
| 5 |
+
Users press "New Episode" and watch the agent navigate all 3 phases.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import random
|
| 11 |
+
import time
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
|
| 14 |
+
import streamlit as st
|
| 15 |
+
|
| 16 |
+
# ── Ensure imports resolve ──────────────────────────────────────────────────
|
| 17 |
+
ROOT = Path(__file__).resolve().parent
|
| 18 |
+
if str(ROOT) not in sys.path:
|
| 19 |
+
sys.path.insert(0, str(ROOT))
|
| 20 |
+
|
| 21 |
+
os.environ.setdefault("SHOPMANAGER_MARKET_MODE", "synthetic")
|
| 22 |
+
|
| 23 |
+
from server.ShopManagerEng_environment import JewelryShopEnvironment
|
| 24 |
+
from models import JewelryAction, PRODUCT_CATALOG
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# ── Page config ─────────────────────────────────────────────────────────────
|
| 28 |
+
st.set_page_config(
|
| 29 |
+
page_title="ShopManagerEng — Jewelry Shop RL",
|
| 30 |
+
page_icon="💎",
|
| 31 |
+
layout="wide",
|
| 32 |
+
initial_sidebar_state="expanded",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
# ── CSS: clean light-theme styling ──────────────────────────────────────────
|
| 36 |
+
st.markdown("""
|
| 37 |
+
<style>
|
| 38 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap');
|
| 39 |
+
html, body, [class*="st-"] { font-family: 'Inter', sans-serif; }
|
| 40 |
+
|
| 41 |
+
.hero-title {
|
| 42 |
+
font-size: 36px; font-weight: 800;
|
| 43 |
+
background: linear-gradient(135deg, #7c3aed, #3b82f6, #10b981);
|
| 44 |
+
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
| 45 |
+
margin-bottom: 2px;
|
| 46 |
+
}
|
| 47 |
+
.hero-sub { font-size: 15px; color: #64748b; margin-bottom: 20px; }
|
| 48 |
+
|
| 49 |
+
.metric-card {
|
| 50 |
+
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px;
|
| 51 |
+
padding: 14px 18px; text-align: center; min-height: 90px;
|
| 52 |
+
}
|
| 53 |
+
.metric-card .label { font-size: 12px; color: #64748b; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
| 54 |
+
.metric-card .value { font-size: 24px; font-weight: 800; color: #1e293b; margin-top: 4px; }
|
| 55 |
+
|
| 56 |
+
.phase-box {
|
| 57 |
+
background: #f1f5f9; border: 1px solid #cbd5e1; border-radius: 12px;
|
| 58 |
+
padding: 18px; margin-bottom: 12px;
|
| 59 |
+
}
|
| 60 |
+
.phase-box.active { border: 2px solid #7c3aed; background: #f5f3ff; }
|
| 61 |
+
.phase-box h4 { margin: 0 0 6px; color: #1e293b; }
|
| 62 |
+
.phase-box p { margin: 0; color: #475569; font-size: 14px; }
|
| 63 |
+
|
| 64 |
+
.env-msg {
|
| 65 |
+
background: #eff6ff; border-left: 4px solid #3b82f6;
|
| 66 |
+
border-radius: 0 10px 10px 0; padding: 12px 16px;
|
| 67 |
+
margin: 10px 0; font-size: 13px; color: #1e40af; line-height: 1.5;
|
| 68 |
+
word-wrap: break-word;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
.step-row {
|
| 72 |
+
padding: 8px 12px; margin: 4px 0; border-radius: 8px;
|
| 73 |
+
font-size: 13px; color: #334155;
|
| 74 |
+
}
|
| 75 |
+
.step-row.market { background: #fef3c7; border-left: 3px solid #f59e0b; }
|
| 76 |
+
.step-row.warehouse { background: #dbeafe; border-left: 3px solid #3b82f6; }
|
| 77 |
+
.step-row.showroom { background: #d1fae5; border-left: 3px solid #10b981; }
|
| 78 |
+
|
| 79 |
+
.reward-big {
|
| 80 |
+
text-align: center; padding: 24px;
|
| 81 |
+
background: linear-gradient(135deg, #f5f3ff, #eff6ff);
|
| 82 |
+
border: 2px solid #7c3aed; border-radius: 16px;
|
| 83 |
+
}
|
| 84 |
+
.reward-big .score { font-size: 52px; font-weight: 800; color: #7c3aed; }
|
| 85 |
+
.reward-big .label { font-size: 14px; color: #64748b; }
|
| 86 |
+
|
| 87 |
+
.catalog-card {
|
| 88 |
+
background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 12px;
|
| 89 |
+
padding: 16px; text-align: center;
|
| 90 |
+
}
|
| 91 |
+
.catalog-card h4 { margin: 0 0 8px; color: #1e293b; }
|
| 92 |
+
.catalog-card p { margin: 2px 0; color: #475569; font-size: 13px; }
|
| 93 |
+
|
| 94 |
+
.demand-bar {
|
| 95 |
+
background: #e2e8f0; border-radius: 6px; height: 10px; overflow: hidden; margin-top: 4px;
|
| 96 |
+
}
|
| 97 |
+
.demand-fill { height: 100%; border-radius: 6px; }
|
| 98 |
+
</style>
|
| 99 |
+
""", unsafe_allow_html=True)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
# ── Heuristic Agent ─────────────────────────────────────────────────────────
|
| 103 |
+
def heuristic_action(obs):
|
| 104 |
+
"""Simple rule-based agent that plays through all 3 phases."""
|
| 105 |
+
if obs.phase == "market":
|
| 106 |
+
price = float(obs.gold_price or 300)
|
| 107 |
+
# Buy if we have enough cash for at least 1 oz
|
| 108 |
+
if obs.cash >= price + 10:
|
| 109 |
+
return JewelryAction(
|
| 110 |
+
market_action="buy",
|
| 111 |
+
gold_qty=1.0,
|
| 112 |
+
target_price_usd=obs.gold_price,
|
| 113 |
+
)
|
| 114 |
+
return JewelryAction(market_action="wait")
|
| 115 |
+
|
| 116 |
+
if obs.phase == "warehouse":
|
| 117 |
+
# Pick the highest-demand product we can afford
|
| 118 |
+
demand = obs.demand or {"ring": 0.5, "necklace": 0.3, "bracelet": 0.2}
|
| 119 |
+
for name in sorted(demand, key=lambda k: demand.get(k, 0), reverse=True):
|
| 120 |
+
spec = PRODUCT_CATALOG[name]
|
| 121 |
+
if obs.gold_oz + 1e-8 >= spec["gold_oz"] and obs.cash >= spec["labor"]:
|
| 122 |
+
return JewelryAction(product_choice=name)
|
| 123 |
+
return JewelryAction(product_choice="ring")
|
| 124 |
+
|
| 125 |
+
if obs.phase == "showroom":
|
| 126 |
+
# Accept if margin > 15% or after round 3
|
| 127 |
+
if (
|
| 128 |
+
obs.current_offer
|
| 129 |
+
and obs.cost_basis > 0
|
| 130 |
+
and float(obs.current_offer) / float(obs.cost_basis) >= 1.15
|
| 131 |
+
) or (obs.negotiation_round and int(obs.negotiation_round) >= 3):
|
| 132 |
+
return JewelryAction(message="I accept")
|
| 133 |
+
offer = float(obs.current_offer or 0)
|
| 134 |
+
if offer:
|
| 135 |
+
return JewelryAction(message=f"How about ${offer * 1.08:.2f}?")
|
| 136 |
+
return JewelryAction(message="I need a better offer")
|
| 137 |
+
|
| 138 |
+
return JewelryAction()
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# ── Session state ───────────────────────────────────────────────────────────
|
| 142 |
+
if "episode_steps" not in st.session_state:
|
| 143 |
+
st.session_state.episode_steps = None
|
| 144 |
+
st.session_state.final_reward = None
|
| 145 |
+
st.session_state.episode_count = 0
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def run_episode(task_id):
|
| 149 |
+
"""Run a full episode with the heuristic agent and return step logs."""
|
| 150 |
+
env = JewelryShopEnvironment()
|
| 151 |
+
seed = random.randint(0, 99999)
|
| 152 |
+
obs = env.reset(seed=seed, market_mode="synthetic", task_id=task_id)
|
| 153 |
+
|
| 154 |
+
steps = [{
|
| 155 |
+
"step": 0, "phase": obs.phase, "action": "reset",
|
| 156 |
+
"msg": obs.message, "reward": 0.0,
|
| 157 |
+
"cash": obs.cash, "gold_oz": obs.gold_oz,
|
| 158 |
+
"gold_price": obs.gold_price,
|
| 159 |
+
"cumulative": float(obs.cumulative_reward),
|
| 160 |
+
}]
|
| 161 |
+
|
| 162 |
+
for i in range(1, 20):
|
| 163 |
+
if obs.done:
|
| 164 |
+
break
|
| 165 |
+
action = heuristic_action(obs)
|
| 166 |
+
|
| 167 |
+
# Describe the action in human terms
|
| 168 |
+
if obs.phase == "market":
|
| 169 |
+
act_str = f"BUY {action.gold_qty} oz" if action.market_action == "buy" else "WAIT"
|
| 170 |
+
elif obs.phase == "warehouse":
|
| 171 |
+
act_str = f"CRAFT {action.product_choice}"
|
| 172 |
+
else:
|
| 173 |
+
act_str = action.message or "..."
|
| 174 |
+
|
| 175 |
+
obs = env.step(action)
|
| 176 |
+
steps.append({
|
| 177 |
+
"step": i, "phase": obs.phase, "action": act_str,
|
| 178 |
+
"msg": obs.message, "reward": float(obs.reward),
|
| 179 |
+
"cash": obs.cash, "gold_oz": obs.gold_oz,
|
| 180 |
+
"gold_price": getattr(obs, "gold_price", 0),
|
| 181 |
+
"product": getattr(obs, "product_for_sale", None),
|
| 182 |
+
"offer": float(obs.current_offer) if obs.current_offer else None,
|
| 183 |
+
"cost_basis": float(obs.cost_basis) if obs.cost_basis else None,
|
| 184 |
+
"cumulative": float(obs.cumulative_reward),
|
| 185 |
+
})
|
| 186 |
+
|
| 187 |
+
return steps, float(obs.cumulative_reward)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# ── Sidebar ─────────────────────────────────────────────────────────────────
|
| 191 |
+
with st.sidebar:
|
| 192 |
+
st.markdown("### 💎 ShopManagerEng")
|
| 193 |
+
st.markdown("---")
|
| 194 |
+
|
| 195 |
+
task = st.selectbox(
|
| 196 |
+
"🎯 Task Profile",
|
| 197 |
+
["profit_negotiator", "market_timing", "demand_crafter"],
|
| 198 |
+
index=0,
|
| 199 |
+
)
|
| 200 |
+
weights = {
|
| 201 |
+
"profit_negotiator": "Showroom 60% · Market 20% · Warehouse 20%",
|
| 202 |
+
"market_timing": "Market 60% · Warehouse 20% · Showroom 20%",
|
| 203 |
+
"demand_crafter": "Warehouse 60% · Market 20% · Showroom 20%",
|
| 204 |
+
}
|
| 205 |
+
st.caption(f"**Weights:** {weights[task]}")
|
| 206 |
+
|
| 207 |
+
st.markdown("---")
|
| 208 |
+
if st.button("🚀 New Episode", use_container_width=True, type="primary"):
|
| 209 |
+
steps, reward = run_episode(task)
|
| 210 |
+
st.session_state.episode_steps = steps
|
| 211 |
+
st.session_state.final_reward = reward
|
| 212 |
+
st.session_state.episode_count += 1
|
| 213 |
+
st.rerun()
|
| 214 |
+
|
| 215 |
+
st.markdown("---")
|
| 216 |
+
st.markdown("#### How It Works")
|
| 217 |
+
st.markdown("""
|
| 218 |
+
An AI **heuristic agent** automatically plays
|
| 219 |
+
through all 3 phases of the jewelry shop:
|
| 220 |
+
|
| 221 |
+
1. 📈 **Market** — Buy gold at the right price
|
| 222 |
+
2. 🏭 **Warehouse** — Craft the most demanded product
|
| 223 |
+
3. 🤝 **Showroom** — Negotiate the best sale price
|
| 224 |
+
|
| 225 |
+
Press **🚀 New Episode** to watch the agent play!
|
| 226 |
+
""")
|
| 227 |
+
|
| 228 |
+
if st.session_state.final_reward is not None:
|
| 229 |
+
st.markdown("---")
|
| 230 |
+
reward = st.session_state.final_reward
|
| 231 |
+
color = "#10b981" if reward >= 0.6 else "#f59e0b" if reward >= 0.4 else "#ef4444"
|
| 232 |
+
st.markdown(f"**Final Score:** :{'green' if reward >= 0.6 else 'orange' if reward >= 0.4 else 'red'}[{reward:.4f}]")
|
| 233 |
+
st.metric("Episodes Played", st.session_state.episode_count)
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
# ── Main area ───────────────────────────────────────────────────────────────
|
| 237 |
+
st.markdown('<p class="hero-title">💎 Jewelry Shop Manager</p>', unsafe_allow_html=True)
|
| 238 |
+
st.markdown('<p class="hero-sub">An RL environment for training LLMs on multi-step business decisions</p>', unsafe_allow_html=True)
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
# ── No episode yet — show welcome ──────────────────────────────────────────
|
| 242 |
+
if st.session_state.episode_steps is None:
|
| 243 |
+
st.info("👋 **Welcome!** Press **🚀 New Episode** in the sidebar to watch the AI agent play through the jewelry shop simulation.")
|
| 244 |
+
|
| 245 |
+
st.markdown("### 📦 Product Catalog")
|
| 246 |
+
cols = st.columns(3)
|
| 247 |
+
items = [("💍 Ring", "ring"), ("📿 Necklace", "necklace"), ("⌚ Bracelet", "bracelet")]
|
| 248 |
+
for i, (icon_name, key) in enumerate(items):
|
| 249 |
+
spec = PRODUCT_CATALOG[key]
|
| 250 |
+
with cols[i]:
|
| 251 |
+
st.markdown(f"""
|
| 252 |
+
<div class="catalog-card">
|
| 253 |
+
<h4>{icon_name}</h4>
|
| 254 |
+
<p>🪙 Gold: {spec['gold_oz']} oz</p>
|
| 255 |
+
<p>🔧 Labor: ${spec['labor']:.0f}</p>
|
| 256 |
+
<p>📊 Base demand: {spec['base_demand']:.0%}</p>
|
| 257 |
+
</div>
|
| 258 |
+
""", unsafe_allow_html=True)
|
| 259 |
+
|
| 260 |
+
st.markdown("### 🧠 Three Business Phases")
|
| 261 |
+
c1, c2, c3 = st.columns(3)
|
| 262 |
+
with c1:
|
| 263 |
+
st.markdown("""
|
| 264 |
+
<div class="phase-box">
|
| 265 |
+
<h4>📈 Phase 1: Market</h4>
|
| 266 |
+
<p>Buy raw gold at the best price. Prices fluctuate — time your purchase wisely!</p>
|
| 267 |
+
</div>
|
| 268 |
+
""", unsafe_allow_html=True)
|
| 269 |
+
with c2:
|
| 270 |
+
st.markdown("""
|
| 271 |
+
<div class="phase-box">
|
| 272 |
+
<h4>🏭 Phase 2: Warehouse</h4>
|
| 273 |
+
<p>Craft a product (ring, necklace, bracelet) that matches market demand.</p>
|
| 274 |
+
</div>
|
| 275 |
+
""", unsafe_allow_html=True)
|
| 276 |
+
with c3:
|
| 277 |
+
st.markdown("""
|
| 278 |
+
<div class="phase-box">
|
| 279 |
+
<h4>🤝 Phase 3: Showroom</h4>
|
| 280 |
+
<p>Negotiate with a customer over 5 rounds to maximize your selling price.</p>
|
| 281 |
+
</div>
|
| 282 |
+
""", unsafe_allow_html=True)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# ── Episode results ─────────────────────────────────────────────────────────
|
| 286 |
+
else:
|
| 287 |
+
steps = st.session_state.episode_steps
|
| 288 |
+
reward = st.session_state.final_reward
|
| 289 |
+
|
| 290 |
+
# ── Score banner ────────────────────────────────────────────────────────
|
| 291 |
+
if reward >= 0.8:
|
| 292 |
+
grade, grade_emoji = "Excellent", "🏆"
|
| 293 |
+
elif reward >= 0.6:
|
| 294 |
+
grade, grade_emoji = "Good", "👍"
|
| 295 |
+
elif reward >= 0.4:
|
| 296 |
+
grade, grade_emoji = "Fair", "😐"
|
| 297 |
+
else:
|
| 298 |
+
grade, grade_emoji = "Poor", "😬"
|
| 299 |
+
|
| 300 |
+
st.markdown(f"""
|
| 301 |
+
<div class="reward-big">
|
| 302 |
+
<div class="label">{grade_emoji} Episode #{st.session_state.episode_count} — {grade}</div>
|
| 303 |
+
<div class="score">{reward:.4f}</div>
|
| 304 |
+
<div class="label">cumulative reward (out of 1.0)</div>
|
| 305 |
+
</div>
|
| 306 |
+
""", unsafe_allow_html=True)
|
| 307 |
+
|
| 308 |
+
st.markdown("")
|
| 309 |
+
|
| 310 |
+
# ── Summary metrics ─────────────────────────────────────────────────────
|
| 311 |
+
last = steps[-1]
|
| 312 |
+
m1, m2, m3, m4 = st.columns(4)
|
| 313 |
+
with m1:
|
| 314 |
+
st.markdown(f"""<div class="metric-card"><div class="label">Steps</div><div class="value">{len(steps)-1}</div></div>""", unsafe_allow_html=True)
|
| 315 |
+
with m2:
|
| 316 |
+
st.markdown(f"""<div class="metric-card"><div class="label">Final Cash</div><div class="value">${last['cash']:,.0f}</div></div>""", unsafe_allow_html=True)
|
| 317 |
+
with m3:
|
| 318 |
+
gold_price = steps[0].get("gold_price", 0)
|
| 319 |
+
st.markdown(f"""<div class="metric-card"><div class="label">Gold Price</div><div class="value">${gold_price:,.0f}/oz</div></div>""", unsafe_allow_html=True)
|
| 320 |
+
with m4:
|
| 321 |
+
product = None
|
| 322 |
+
for s in steps:
|
| 323 |
+
if s.get("product"):
|
| 324 |
+
product = s["product"]
|
| 325 |
+
st.markdown(f"""<div class="metric-card"><div class="label">Product</div><div class="value">{(product or 'N/A').title()}</div></div>""", unsafe_allow_html=True)
|
| 326 |
+
|
| 327 |
+
st.markdown("---")
|
| 328 |
+
|
| 329 |
+
# ── Step-by-step log ────────────────────────────────────────────────────
|
| 330 |
+
st.markdown("### 📋 Agent Decision Log")
|
| 331 |
+
|
| 332 |
+
for s in steps:
|
| 333 |
+
phase = s.get("phase", "market")
|
| 334 |
+
icon = {"market": "📈", "warehouse": "🏭", "showroom": "🤝"}.get(phase, "⬜")
|
| 335 |
+
step_num = s["step"]
|
| 336 |
+
action = s.get("action", "")
|
| 337 |
+
rw = s.get("reward", 0)
|
| 338 |
+
cum = s.get("cumulative", 0)
|
| 339 |
+
|
| 340 |
+
rw_badge = f" · reward: `{rw:.4f}`" if rw else ""
|
| 341 |
+
cum_str = f" · cumulative: `{cum:.4f}`" if cum else ""
|
| 342 |
+
|
| 343 |
+
# Use pure markdown — no raw HTML divs
|
| 344 |
+
st.markdown(f"**Step {step_num}** {icon} **{action}**{rw_badge}{cum_str}")
|
| 345 |
+
|
| 346 |
+
# Show environment message
|
| 347 |
+
if s.get("msg"):
|
| 348 |
+
st.caption(s["msg"])
|
| 349 |
+
|
| 350 |
+
st.divider()
|
| 351 |
+
|
| 352 |
+
# ── Reward progression chart ────────────────────────────────────────────
|
| 353 |
+
st.markdown("---")
|
| 354 |
+
st.markdown("### 📊 Cumulative Reward Over Steps")
|
| 355 |
+
chart_data = [s["cumulative"] for s in steps]
|
| 356 |
+
st.line_chart(chart_data, use_container_width=True, height=200)
|
| 357 |
+
|
| 358 |
+
st.info("Press **🚀 New Episode** in the sidebar to run another episode with a different seed!")
|
uv.lock
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|