watercolour-env / openenv.yaml
sergiopaniego's picture
sergiopaniego HF Staff
Canonical env: reward weights from the environment, pool from the pinned dataset
2ac71c7 verified
Raw
History Blame Contribute Delete
5.17 kB
spec_version: 1
name: watercolour_env
type: space
runtime: fastapi
app: server.app:app
port: 8000
description: >
Blind watercolour painting. The model is asked for a p5.brush sketch and never
sees the result, so it has to hold a composition in its head and emit code for
it. The sketch is painted in a headless browser and weighed against a pool of
reference paintings by a vision judge, which is asked which of two paintings is
the better watercolour rather than to rate one in the abstract.
version: "0.1.0"
# Environment configuration defaults, overridable via constructor arguments.
environment:
subject: null # pin the subject, or null to sample
enable_judge: true # false scores on gate admission alone, with no vision calls
references: 2 # references per submission; cost is two vision calls each
return_image: false # include the painting in the observation
# Public Space variables, applied by `openenv push`.
variables:
WATERCOLOUR_JUDGE_MODEL: Qwen/Qwen3-VL-30B-A3B-Instruct
# Secrets never live in this file. Pass them at push time, where the value is
# not logged:
#
# openenv push envs/watercolour_env --secret HF_TOKEN=hf_...
#
# HF_TOKEN enables the judge. Without it the Space still runs, scores on gate
# admission alone, and sets judged=false on every observation so a harness can
# tell "painted badly" from "nobody looked at it".
#
# WATERCOLOUR_DISABLE_JUDGE=1 forces gate-only scoring even when a token is
# present.
# The gate is free and runs first. Only what clears it reaches the judge, so a
# run against a model producing garbage stays cheap.
rubric:
type: sequential
name: WatercolourRubric
# Weights from the final rubric in Narreddi's write-up. HPSv3 (0.30) is not
# implemented, so the maximum here is 0.70 rather than 1.0. Left unnormalised
# on purpose: renormalising would silently rebalance what remains.
layers:
- name: gate
weight: 0.05
cost: free
description: >
Source inspection and rendering. Rejects replies with no sketch,
truncated source, a non-WEBGL canvas, painting with bare p5 primitives
instead of the library, loading an external image, writing the answer as
text, and canvases that came back blank. Failing it scores zero outright.
Clearing it earns 0.05 on its own, which is the only term a policy can
earn before it paints well, and so the only reward variance a GRPO group
has on day one.
- name: length
weight: 0.05
cost: free
description: >
Binary check that the sketch is neither a stub nor a runaway. Their
target was around 3,000 tokens, reflecting a much larger model writing
13,500-token sketches; measured here a 4B writes 234 to 293 tokens and
the reference paintings run 202 to 315, so the band is calibrated to
those and this term is close to constant with a small model.
- name: judge
weight: 0.60
cost: two vision calls per reference
description: >
Comparative scoring against a sampled reference pool. Each comparison
runs in both presentation orders, because both judges measured picked
whichever image came first whenever they had no real preference. Winning
both orders scores one, losing both scores zero, and a split scores half.
- name: hpsv3
weight: 0.30
cost: not implemented
description: >
A human preference model, carrying 0.30 in the original rubric and absent
here. It scores a painting on its own rather than against a reference, so
it is what gives a policy something to climb before it can beat anything
in the pool, and its absence is the first thing to suspect if the reward
sits flat at the floor.
Not implemented because it is an infrastructure decision rather than a
few lines of code. MizzenAI/HPSv3 is a 7B Qwen2.5-VL fine-tune weighing
16.6 GB, it is served by its own `hpsv3` package rather than through a
standard text-generation endpoint, and it is on no inference provider. It
will not fit beside a 4B LoRA run on a 24 GB card and it is far too slow
to run on a CPU Space, so adding it means either a bigger training
flavour or a second GPU service for the environment to call.
The cheaper thing to try first is a reference pool that spans a quality
range, since a pool with a floor gives a small policy something to beat
without any of that. If the reward is still flat with a tiered pool, this
is the answer and the cost above is what it costs.
# Anti-gaming rules enforced before any scoring happens.
gate_violations:
- no_sketch_in_response
- truncated
- missing_entry_points
- not_webgl
- external_access
- text_label
- bare_primitives
- no_painting_calls
- render_failed
- blank_canvas
metrics:
reward:
- reward/total
- reward/judge
render:
- render/paint_fraction
- render/finished
- render/elapsed_ms
judge:
- judge/score
- judge/unavailable
episode:
- episode/task_id
- episode/gate_passed