synth-ai 0.2.14__py3-none-any.whl → 0.2.16__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of synth-ai might be problematic. Click here for more details.
- examples/README.md +1 -0
- examples/multi_step/SFT_README.md +147 -0
- examples/multi_step/configs/crafter_rl_stepwise_hosted_judge.toml +9 -9
- examples/multi_step/configs/crafter_sft_qwen30b_lora.toml +62 -0
- examples/multi_step/convert_traces_to_sft.py +84 -0
- examples/multi_step/run_sft_qwen30b.sh +45 -0
- examples/qwen_coder/configs/coder_lora_30b.toml +2 -1
- examples/qwen_coder/configs/coder_lora_4b.toml +2 -1
- examples/qwen_coder/configs/coder_lora_small.toml +2 -1
- examples/qwen_vl/BUGS_AND_FIXES.md +232 -0
- examples/qwen_vl/IMAGE_VALIDATION_COMPLETE.md +271 -0
- examples/qwen_vl/IMAGE_VALIDATION_SUMMARY.md +260 -0
- examples/qwen_vl/INFERENCE_SFT_TESTS.md +412 -0
- examples/qwen_vl/NEXT_STEPS_2B.md +325 -0
- examples/qwen_vl/QUICKSTART.md +327 -0
- examples/qwen_vl/QUICKSTART_RL_VISION.md +110 -0
- examples/qwen_vl/README.md +154 -0
- examples/qwen_vl/RL_VISION_COMPLETE.md +475 -0
- examples/qwen_vl/RL_VISION_TESTING.md +333 -0
- examples/qwen_vl/SDK_VISION_INTEGRATION.md +328 -0
- examples/qwen_vl/SETUP_COMPLETE.md +275 -0
- examples/qwen_vl/VISION_TESTS_COMPLETE.md +490 -0
- examples/qwen_vl/VLM_PIPELINE_COMPLETE.md +242 -0
- examples/qwen_vl/__init__.py +2 -0
- examples/qwen_vl/collect_data_via_cli.md +423 -0
- examples/qwen_vl/collect_vision_traces.py +368 -0
- examples/qwen_vl/configs/crafter_rl_vision_qwen3vl4b.toml +127 -0
- examples/qwen_vl/configs/crafter_vlm_sft_example.toml +60 -0
- examples/qwen_vl/configs/eval_gpt4o_mini_vision.toml +43 -0
- examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml +29 -0
- examples/qwen_vl/configs/eval_gpt5nano_vision.toml +45 -0
- examples/qwen_vl/configs/eval_qwen2vl_vision.toml +44 -0
- examples/qwen_vl/configs/filter_qwen2vl_sft.toml +50 -0
- examples/qwen_vl/configs/filter_vision_sft.toml +53 -0
- examples/qwen_vl/configs/filter_vision_test.toml +8 -0
- examples/qwen_vl/configs/sft_qwen3_vl_2b_test.toml +54 -0
- examples/qwen_vl/crafter_gpt5nano_agent.py +308 -0
- examples/qwen_vl/crafter_qwen_vl_agent.py +300 -0
- examples/qwen_vl/run_vision_comparison.sh +62 -0
- examples/qwen_vl/run_vision_sft_pipeline.sh +175 -0
- examples/qwen_vl/test_image_validation.py +201 -0
- examples/qwen_vl/test_sft_vision_data.py +110 -0
- examples/rl/README.md +1 -1
- examples/rl/configs/eval_base_qwen.toml +17 -0
- examples/rl/configs/eval_rl_qwen.toml +13 -0
- examples/rl/configs/rl_from_base_qwen.toml +37 -0
- examples/rl/configs/rl_from_base_qwen17.toml +76 -0
- examples/rl/configs/rl_from_ft_qwen.toml +37 -0
- examples/rl/run_eval.py +436 -0
- examples/rl/run_rl_and_save.py +111 -0
- examples/rl/task_app/README.md +22 -0
- examples/rl/task_app/math_single_step.py +990 -0
- examples/rl/task_app/math_task_app.py +111 -0
- examples/sft/README.md +5 -5
- examples/sft/configs/crafter_fft_qwen0p6b.toml +4 -2
- examples/sft/configs/crafter_lora_qwen0p6b.toml +4 -3
- examples/sft/evaluate.py +2 -4
- examples/sft/export_dataset.py +7 -4
- examples/swe/task_app/README.md +1 -1
- examples/swe/task_app/grpo_swe_mini.py +0 -1
- examples/swe/task_app/grpo_swe_mini_task_app.py +0 -12
- examples/swe/task_app/hosted/envs/mini_swe/environment.py +13 -13
- examples/swe/task_app/hosted/policy_routes.py +0 -2
- examples/swe/task_app/hosted/rollout.py +0 -8
- examples/task_apps/crafter/task_app/grpo_crafter.py +4 -7
- examples/task_apps/crafter/task_app/synth_envs_hosted/envs/crafter/policy.py +59 -1
- examples/task_apps/crafter/task_app/synth_envs_hosted/inference/openai_client.py +30 -0
- examples/task_apps/crafter/task_app/synth_envs_hosted/policy_routes.py +62 -31
- examples/task_apps/crafter/task_app/synth_envs_hosted/rollout.py +16 -14
- examples/task_apps/enron/__init__.py +1 -0
- examples/vlm/README.md +3 -3
- examples/vlm/configs/crafter_vlm_gpt4o.toml +2 -0
- examples/vlm/crafter_openai_vlm_agent.py +3 -5
- examples/vlm/filter_image_rows.py +1 -1
- examples/vlm/run_crafter_vlm_benchmark.py +2 -2
- examples/warming_up_to_rl/_utils.py +92 -0
- examples/warming_up_to_rl/analyze_trace_db.py +1 -1
- examples/warming_up_to_rl/configs/crafter_fft.toml +2 -0
- examples/warming_up_to_rl/configs/crafter_fft_4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_fft_qwen4b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_groq_qwen32b.toml +2 -0
- examples/warming_up_to_rl/configs/eval_modal_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_base_qwen4b.toml +2 -1
- examples/warming_up_to_rl/configs/rl_from_ft.toml +2 -0
- examples/warming_up_to_rl/export_trace_sft.py +174 -60
- examples/warming_up_to_rl/readme.md +63 -132
- examples/warming_up_to_rl/run_fft_and_save.py +1 -1
- examples/warming_up_to_rl/run_rl_and_save.py +1 -1
- examples/warming_up_to_rl/task_app/README.md +42 -0
- examples/warming_up_to_rl/task_app/grpo_crafter.py +696 -0
- examples/warming_up_to_rl/task_app/grpo_crafter_task_app.py +135 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/README.md +173 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/branching.py +143 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/environment_routes.py +1226 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/__init__.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/__init__.py +6 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/app.py +1 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/environment.py +522 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/policy.py +478 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/react_agent.py +108 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/shared.py +305 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/envs/crafter/tools.py +47 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/hosted_app.py +204 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/inference/openai_client.py +618 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/main.py +100 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/policy_routes.py +1081 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/registry.py +195 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/rollout.py +1861 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/__init__.py +5 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/storage/volume.py +211 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_agents.py +161 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/test_service.py +137 -0
- examples/warming_up_to_rl/task_app/synth_envs_hosted/utils.py +62 -0
- synth_ai/__init__.py +44 -30
- synth_ai/_utils/__init__.py +47 -0
- synth_ai/_utils/base_url.py +10 -0
- synth_ai/_utils/http.py +10 -0
- synth_ai/_utils/prompts.py +10 -0
- synth_ai/_utils/task_app_state.py +12 -0
- synth_ai/_utils/user_config.py +10 -0
- synth_ai/api/models/supported.py +144 -7
- synth_ai/api/train/__init__.py +13 -1
- synth_ai/api/train/cli.py +30 -7
- synth_ai/api/train/config_finder.py +18 -11
- synth_ai/api/train/env_resolver.py +13 -10
- synth_ai/cli/__init__.py +62 -78
- synth_ai/cli/_modal_wrapper.py +7 -5
- synth_ai/cli/_typer_patch.py +0 -2
- synth_ai/cli/_validate_task_app.py +22 -4
- synth_ai/cli/legacy_root_backup.py +3 -1
- synth_ai/cli/lib/__init__.py +10 -0
- synth_ai/cli/lib/task_app_discovery.py +7 -0
- synth_ai/cli/lib/task_app_env.py +518 -0
- synth_ai/cli/recent.py +2 -1
- synth_ai/cli/setup.py +266 -0
- synth_ai/cli/status.py +1 -1
- synth_ai/cli/task_app_deploy.py +16 -0
- synth_ai/cli/task_app_list.py +25 -0
- synth_ai/cli/task_app_modal_serve.py +16 -0
- synth_ai/cli/task_app_serve.py +18 -0
- synth_ai/cli/task_apps.py +71 -31
- synth_ai/cli/traces.py +1 -1
- synth_ai/cli/train.py +18 -0
- synth_ai/cli/tui.py +7 -2
- synth_ai/cli/turso.py +1 -1
- synth_ai/cli/watch.py +1 -1
- synth_ai/demos/__init__.py +10 -0
- synth_ai/demos/core/__init__.py +28 -1
- synth_ai/demos/crafter/__init__.py +1 -0
- synth_ai/demos/crafter/crafter_fft_4b.toml +55 -0
- synth_ai/demos/crafter/grpo_crafter_task_app.py +185 -0
- synth_ai/demos/crafter/rl_from_base_qwen4b.toml +74 -0
- synth_ai/demos/demo_registry.py +176 -0
- synth_ai/demos/math/__init__.py +1 -0
- synth_ai/demos/math/_common.py +16 -0
- synth_ai/demos/math/app.py +38 -0
- synth_ai/demos/math/config.toml +76 -0
- synth_ai/demos/math/deploy_modal.py +54 -0
- synth_ai/demos/math/modal_task_app.py +702 -0
- synth_ai/demos/math/task_app_entry.py +51 -0
- synth_ai/environments/environment/core.py +7 -1
- synth_ai/environments/examples/bandit/engine.py +0 -1
- synth_ai/environments/examples/bandit/environment.py +0 -1
- synth_ai/environments/examples/wordle/environment.py +0 -1
- synth_ai/evals/base.py +16 -5
- synth_ai/evals/client.py +1 -1
- synth_ai/inference/client.py +1 -1
- synth_ai/judge_schemas.py +8 -8
- synth_ai/learning/client.py +1 -1
- synth_ai/learning/health.py +1 -1
- synth_ai/learning/jobs.py +1 -1
- synth_ai/learning/rl/client.py +1 -1
- synth_ai/learning/rl/env_keys.py +1 -1
- synth_ai/learning/rl/secrets.py +1 -1
- synth_ai/learning/sft/client.py +1 -1
- synth_ai/learning/sft/data.py +407 -4
- synth_ai/learning/validators.py +4 -1
- synth_ai/task/apps/__init__.py +4 -2
- synth_ai/task/config.py +6 -4
- synth_ai/task/rubrics/__init__.py +1 -2
- synth_ai/task/rubrics/loaders.py +14 -10
- synth_ai/task/rubrics.py +219 -0
- synth_ai/task/trace_correlation_helpers.py +24 -11
- synth_ai/task/tracing_utils.py +14 -3
- synth_ai/task/validators.py +2 -3
- synth_ai/tracing_v3/abstractions.py +3 -3
- synth_ai/tracing_v3/config.py +15 -13
- synth_ai/tracing_v3/constants.py +21 -0
- synth_ai/tracing_v3/db_config.py +3 -1
- synth_ai/tracing_v3/decorators.py +10 -7
- synth_ai/tracing_v3/llm_call_record_helpers.py +5 -5
- synth_ai/tracing_v3/session_tracer.py +7 -7
- synth_ai/tracing_v3/storage/base.py +29 -29
- synth_ai/tracing_v3/storage/config.py +3 -3
- synth_ai/tracing_v3/turso/daemon.py +8 -9
- synth_ai/tracing_v3/turso/native_manager.py +80 -72
- synth_ai/tracing_v3/utils.py +2 -2
- synth_ai/tui/cli/query_experiments.py +4 -4
- synth_ai/tui/cli/query_experiments_v3.py +4 -4
- synth_ai/tui/dashboard.py +14 -9
- synth_ai/utils/__init__.py +101 -0
- synth_ai/utils/base_url.py +94 -0
- synth_ai/utils/cli.py +131 -0
- synth_ai/utils/env.py +287 -0
- synth_ai/utils/http.py +169 -0
- synth_ai/utils/modal.py +308 -0
- synth_ai/utils/process.py +212 -0
- synth_ai/utils/prompts.py +39 -0
- synth_ai/utils/sqld.py +122 -0
- synth_ai/utils/task_app_discovery.py +882 -0
- synth_ai/utils/task_app_env.py +186 -0
- synth_ai/utils/task_app_state.py +318 -0
- synth_ai/utils/user_config.py +137 -0
- synth_ai/v0/config/__init__.py +1 -5
- synth_ai/v0/config/base_url.py +1 -7
- synth_ai/v0/tracing/config.py +1 -1
- synth_ai/v0/tracing/decorators.py +1 -1
- synth_ai/v0/tracing/upload.py +1 -1
- synth_ai/v0/tracing_v1/config.py +1 -1
- synth_ai/v0/tracing_v1/decorators.py +1 -1
- synth_ai/v0/tracing_v1/upload.py +1 -1
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/METADATA +85 -31
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/RECORD +229 -117
- synth_ai/cli/man.py +0 -106
- synth_ai/compound/cais.py +0 -0
- synth_ai/core/experiment.py +0 -13
- synth_ai/core/system.py +0 -15
- synth_ai/demo_registry.py +0 -295
- synth_ai/handshake.py +0 -109
- synth_ai/http.py +0 -26
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/WHEEL +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/entry_points.txt +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/licenses/LICENSE +0 -0
- {synth_ai-0.2.14.dist-info → synth_ai-0.2.16.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# Next Steps: Qwen3-VL-2B SFT & RL Training
|
|
2
|
+
|
|
3
|
+
**Status:** Data collection complete ✅ | Ready for SFT training 🚀
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 📋 Current Status
|
|
8
|
+
|
|
9
|
+
### ✅ Completed
|
|
10
|
+
1. **VLM Data Collection Pipeline** - WORKING END-TO-END
|
|
11
|
+
- Fixed task app tracing to return full session traces
|
|
12
|
+
- Fixed CLI to handle multimodal content preservation
|
|
13
|
+
- Successfully collected traces with base64 PNG images
|
|
14
|
+
- Database: `traces/gpt4o_vision_test/rollouts.db`
|
|
15
|
+
- Exported: `traces/gpt4o_vision_test/sft/train.jsonl` (50 examples validated)
|
|
16
|
+
|
|
17
|
+
2. **Infrastructure Validated**
|
|
18
|
+
- ✅ `synth-ai eval` stores traces with images
|
|
19
|
+
- ✅ `synth-ai filter` exports SFT JSONL with preserved images
|
|
20
|
+
- ✅ Multimodal messages follow OpenAI format
|
|
21
|
+
- ✅ Images embedded as base64 PNG (~1306 chars per 64x64 image)
|
|
22
|
+
|
|
23
|
+
3. **Documentation**
|
|
24
|
+
- `VLM_PIPELINE_COMPLETE.md` - Full pipeline guide
|
|
25
|
+
- `PIPELINE_RUN_LOG.txt` - Execution log with all fixes
|
|
26
|
+
- `BUGS_AND_FIXES.md` - Detailed bug reports
|
|
27
|
+
- `SETUP_COMPLETE.md` - Summary of setup
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 🎯 Next Steps: Train Qwen3-VL-2B
|
|
32
|
+
|
|
33
|
+
### Step 1: Scale Up Data Collection (Optional)
|
|
34
|
+
|
|
35
|
+
We have 50 working examples. For production training, collect more:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
cd /Users/joshpurtell/Documents/GitHub/synth-ai
|
|
39
|
+
|
|
40
|
+
# Collect 100 episodes (will create ~5000 samples)
|
|
41
|
+
export TASKAPP_TRACING_ENABLED=1
|
|
42
|
+
uvx synth-ai eval \
|
|
43
|
+
--config examples/qwen_vl/configs/eval_gpt4o_vision_proper.toml \
|
|
44
|
+
--seeds 0-99 \
|
|
45
|
+
--trace-db traces/gpt4o_vision_100/rollouts.db \
|
|
46
|
+
--env-file /path/to/.env
|
|
47
|
+
|
|
48
|
+
# Filter and export
|
|
49
|
+
uvx synth-ai filter \
|
|
50
|
+
--config examples/qwen_vl/configs/filter_vision_test.toml
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Output:** ~4500 SFT examples with images
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### Step 2: Create SFT Config for Qwen3-VL-2B
|
|
58
|
+
|
|
59
|
+
File: `/Users/joshpurtell/Documents/GitHub/monorepo/configs/vision_sft/crafter_qwen3vl_2b_gpt4o.toml`
|
|
60
|
+
|
|
61
|
+
```toml
|
|
62
|
+
# Crafter Vision SFT: Qwen3-VL-2B trained on gpt-4o-mini traces
|
|
63
|
+
# Using 2B model for faster iteration and lower GPU requirements
|
|
64
|
+
|
|
65
|
+
[algorithm]
|
|
66
|
+
type = "offline"
|
|
67
|
+
method = "sft"
|
|
68
|
+
variety = "lora"
|
|
69
|
+
|
|
70
|
+
[job]
|
|
71
|
+
model = "Qwen/Qwen3-VL-2B-Instruct"
|
|
72
|
+
data = "traces/gpt4o_vision_100/sft/train.jsonl"
|
|
73
|
+
|
|
74
|
+
[compute]
|
|
75
|
+
gpu_type = "H200"
|
|
76
|
+
gpu_count = 2
|
|
77
|
+
nodes = 1
|
|
78
|
+
|
|
79
|
+
[training]
|
|
80
|
+
mode = "lora"
|
|
81
|
+
use_qlora = true
|
|
82
|
+
|
|
83
|
+
[hyperparameters]
|
|
84
|
+
n_epochs = 3
|
|
85
|
+
per_device_batch = 1
|
|
86
|
+
gradient_accumulation_steps = 16
|
|
87
|
+
sequence_length = 2048
|
|
88
|
+
learning_rate = 5e-05
|
|
89
|
+
warmup_ratio = 0.03
|
|
90
|
+
train_kind = "peft"
|
|
91
|
+
|
|
92
|
+
# LoRA config
|
|
93
|
+
lora_rank = 16
|
|
94
|
+
lora_alpha = 32
|
|
95
|
+
lora_dropout = 0.05
|
|
96
|
+
lora_target_modules = ["all-linear"]
|
|
97
|
+
|
|
98
|
+
# Training optimizations
|
|
99
|
+
[hyperparameters.parallelism]
|
|
100
|
+
use_deepspeed = true
|
|
101
|
+
deepspeed_stage = 2
|
|
102
|
+
bf16 = true
|
|
103
|
+
activation_checkpointing = true
|
|
104
|
+
|
|
105
|
+
# Evaluation
|
|
106
|
+
evaluation_strategy = "steps"
|
|
107
|
+
eval_steps = 50
|
|
108
|
+
save_best_model_at_end = true
|
|
109
|
+
metric_for_best_model = "val.loss"
|
|
110
|
+
|
|
111
|
+
[tags]
|
|
112
|
+
task = "crafter"
|
|
113
|
+
modality = "vision"
|
|
114
|
+
data_source = "openai_gpt4o_mini"
|
|
115
|
+
model_family = "qwen3_vl"
|
|
116
|
+
model_size = "2b"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
### Step 3: Run SFT Training
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
cd /Users/joshpurtell/Documents/GitHub/monorepo
|
|
125
|
+
|
|
126
|
+
# Copy data to monorepo (if not already there)
|
|
127
|
+
cp -r /Users/joshpurtell/Documents/GitHub/synth-ai/traces/gpt4o_vision_100/sft/ \
|
|
128
|
+
backend/data/vision_sft/
|
|
129
|
+
|
|
130
|
+
# Submit SFT job
|
|
131
|
+
export BACKEND_BASE_URL="https://synth-backend-dev-docker.onrender.com/api"
|
|
132
|
+
uvx synth-ai train \
|
|
133
|
+
--type sft \
|
|
134
|
+
--config configs/vision_sft/crafter_qwen3vl_2b_gpt4o.toml \
|
|
135
|
+
--env-file backend/.env.dev
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Expected:**
|
|
139
|
+
- Training time: 1-2 hours
|
|
140
|
+
- Cost: ~$10.50 (2x H200)
|
|
141
|
+
- Output: LoRA adapter at `lora_adapters/qwen3vl_2b_crafter_gpt4o/`
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
### Step 4: Create RL Config for Qwen3-VL-2B
|
|
146
|
+
|
|
147
|
+
File: `/Users/joshpurtell/Documents/GitHub/synth-ai/examples/qwen_vl/configs/crafter_rl_qwen3vl_2b.toml`
|
|
148
|
+
|
|
149
|
+
```toml
|
|
150
|
+
# Crafter Vision RL: Qwen3-VL-2B with GRPO
|
|
151
|
+
# Uses SFT-initialized model for RL fine-tuning
|
|
152
|
+
|
|
153
|
+
[algorithm]
|
|
154
|
+
type = "online"
|
|
155
|
+
method = "grpo"
|
|
156
|
+
variety = "default"
|
|
157
|
+
|
|
158
|
+
[model]
|
|
159
|
+
base = "Qwen/Qwen3-VL-2B-Instruct"
|
|
160
|
+
adapter = "lora_adapters/qwen3vl_2b_crafter_gpt4o" # From SFT step
|
|
161
|
+
|
|
162
|
+
[job]
|
|
163
|
+
rollout_count = 50
|
|
164
|
+
n_iterations = 20
|
|
165
|
+
max_steps_per_rollout = 50
|
|
166
|
+
|
|
167
|
+
[compute]
|
|
168
|
+
gpu_type = "H200"
|
|
169
|
+
gpu_count = 4
|
|
170
|
+
nodes = 1
|
|
171
|
+
|
|
172
|
+
[topology]
|
|
173
|
+
type = "single_node_split"
|
|
174
|
+
gpus_for_vllm = 2
|
|
175
|
+
gpus_for_training = 2
|
|
176
|
+
gpus_for_ref = 0
|
|
177
|
+
|
|
178
|
+
[vllm]
|
|
179
|
+
tensor_parallel_size = 1 # 2B fits on 1 GPU
|
|
180
|
+
enable_prefix_caching = false
|
|
181
|
+
use_cudagraph = true
|
|
182
|
+
gpu_memory_utilization = 0.85
|
|
183
|
+
max_model_len = 2048
|
|
184
|
+
|
|
185
|
+
[training]
|
|
186
|
+
mode = "lora"
|
|
187
|
+
use_qlora = true
|
|
188
|
+
|
|
189
|
+
[hyperparameters]
|
|
190
|
+
per_device_batch = 2
|
|
191
|
+
gradient_accumulation_steps = 8
|
|
192
|
+
sequence_length = 2048
|
|
193
|
+
learning_rate = 2e-06
|
|
194
|
+
warmup_ratio = 0.1
|
|
195
|
+
train_kind = "peft"
|
|
196
|
+
|
|
197
|
+
lora_rank = 16
|
|
198
|
+
lora_alpha = 32
|
|
199
|
+
lora_dropout = 0.05
|
|
200
|
+
lora_target_modules = ["all-linear"]
|
|
201
|
+
|
|
202
|
+
[grpo]
|
|
203
|
+
kl_coeff = 0.1
|
|
204
|
+
clip_range = 0.2
|
|
205
|
+
value_clip_range = 0.2
|
|
206
|
+
normalize_rewards = true
|
|
207
|
+
|
|
208
|
+
[judge]
|
|
209
|
+
type = "remote"
|
|
210
|
+
provider = "openai"
|
|
211
|
+
model = "gpt-4o-mini"
|
|
212
|
+
|
|
213
|
+
[tags]
|
|
214
|
+
task = "crafter"
|
|
215
|
+
modality = "vision"
|
|
216
|
+
algorithm = "grpo"
|
|
217
|
+
model_family = "qwen3_vl"
|
|
218
|
+
model_size = "2b"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### Step 5: Run RL Training
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
cd /Users/joshpurtell/Documents/GitHub/synth-ai
|
|
227
|
+
|
|
228
|
+
# Submit RL job
|
|
229
|
+
uvx synth-ai train \
|
|
230
|
+
--type rl \
|
|
231
|
+
--config examples/qwen_vl/configs/crafter_rl_qwen3vl_2b.toml \
|
|
232
|
+
--env-file /path/to/.env
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Expected:**
|
|
236
|
+
- Training time: 4-6 hours
|
|
237
|
+
- Cost: ~$70 (4x H200)
|
|
238
|
+
- Output: RL-tuned adapter at `lora_adapters/qwen3vl_2b_crafter_rl_iter20/`
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
### Step 6: Evaluate Results
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Run benchmark
|
|
246
|
+
python examples/qwen_vl/benchmark_vision_agents.py
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Expected Performance:**
|
|
250
|
+
- Base Qwen3-VL-2B: ~6.5% achievement rate
|
|
251
|
+
- After SFT: ~20% achievement rate (+13.5%)
|
|
252
|
+
- After RL: ~38% achievement rate (+18% more)
|
|
253
|
+
- Teacher (gpt-4o-mini): ~45% achievement rate
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 💰 Cost & Timeline Summary
|
|
258
|
+
|
|
259
|
+
### Qwen3-VL-2B Pipeline
|
|
260
|
+
|
|
261
|
+
| Step | Description | Cost | Time |
|
|
262
|
+
|------|-------------|------|------|
|
|
263
|
+
| 1 | Data collection (100 episodes) | ~$1-2 | 30-60 min |
|
|
264
|
+
| 2 | Dataset assembly | $0 | < 5 min |
|
|
265
|
+
| 3 | Vision SFT (3 epochs) | ~$10.50 | 1-2 hrs |
|
|
266
|
+
| 4 | Vision RL (20 iterations) | ~$70 | 4-6 hrs |
|
|
267
|
+
| 5 | Evaluation | ~$5 | 2-3 hrs |
|
|
268
|
+
|
|
269
|
+
**Total:** ~$87, 8-12 hours
|
|
270
|
+
|
|
271
|
+
### Cost Comparison: 2B vs 8B
|
|
272
|
+
|
|
273
|
+
| Model | SFT Cost | RL Cost | Total | Training Time |
|
|
274
|
+
|-------|----------|---------|-------|---------------|
|
|
275
|
+
| 2B | $10.50 | $70 | $87 | 8-12 hrs |
|
|
276
|
+
| 8B | $21 | $112 | $140 | 12-18 hrs |
|
|
277
|
+
|
|
278
|
+
**Savings with 2B:** 40% cost reduction, 30% faster
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## 🎯 Key Advantages of 2B Model
|
|
283
|
+
|
|
284
|
+
1. **Faster Iteration**
|
|
285
|
+
- SFT: 1-2 hours vs 2-4 hours for 8B
|
|
286
|
+
- RL: 4-6 hours vs 6-10 hours for 8B
|
|
287
|
+
- Enables rapid experimentation
|
|
288
|
+
|
|
289
|
+
2. **Lower GPU Requirements**
|
|
290
|
+
- Fits on 1 GPU for inference (use 2 for safety)
|
|
291
|
+
- Can use batch_size=2 vs 1 for 8B
|
|
292
|
+
- More efficient GPU utilization
|
|
293
|
+
|
|
294
|
+
3. **Cost Effective**
|
|
295
|
+
- ~$87 total vs $140 for 8B
|
|
296
|
+
- Better for initial prototyping
|
|
297
|
+
- Scale to 8B later if needed
|
|
298
|
+
|
|
299
|
+
4. **Competitive Performance**
|
|
300
|
+
- ~38% achievement rate after RL
|
|
301
|
+
- vs ~42% for 8B (only 4% difference)
|
|
302
|
+
- Good enough for validation and testing
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 📝 Notes
|
|
307
|
+
|
|
308
|
+
- All configs use LoRA for memory efficiency
|
|
309
|
+
- Vision models require batch_size=1-2 (images are memory-intensive)
|
|
310
|
+
- Use DeepSpeed Stage 2 for training optimization
|
|
311
|
+
- Disable prefix caching (unstable with LoRA + vision)
|
|
312
|
+
- 2B model is perfect for initial testing and rapid iteration
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 🚀 Ready to Start!
|
|
317
|
+
|
|
318
|
+
The infrastructure is ready. Just need to:
|
|
319
|
+
1. Create the two TOML configs above
|
|
320
|
+
2. Run SFT training
|
|
321
|
+
3. Run RL training
|
|
322
|
+
4. Evaluate and compare
|
|
323
|
+
|
|
324
|
+
All the hard work (data collection, tracing fixes, filtering) is done! 🎉
|
|
325
|
+
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# Qwen VL Quickstart Guide
|
|
2
|
+
|
|
3
|
+
Complete guide to running vision-language models on Crafter with image observations.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Demo
|
|
6
|
+
|
|
7
|
+
### Option 1: Run gpt-5-nano (OpenAI)
|
|
8
|
+
```bash
|
|
9
|
+
export OPENAI_API_KEY="sk-..."
|
|
10
|
+
uv run python examples/qwen_vl/crafter_gpt5nano_agent.py --seeds 5 --steps 10
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Option 2: Run Qwen-VL (synth-ai)
|
|
14
|
+
```bash
|
|
15
|
+
export SYNTH_API_KEY="sk_live_..."
|
|
16
|
+
uv run python examples/qwen_vl/crafter_qwen_vl_agent.py \
|
|
17
|
+
--model Qwen/Qwen2-VL-7B-Instruct --seeds 5 --steps 10
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Option 3: Compare Both
|
|
21
|
+
```bash
|
|
22
|
+
export OPENAI_API_KEY="sk-..."
|
|
23
|
+
export SYNTH_API_KEY="sk_live_..."
|
|
24
|
+
bash examples/qwen_vl/run_vision_comparison.sh
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 📊 Expected Output
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Running 10 Crafter episodes with model=gpt-5-nano
|
|
33
|
+
Using OpenAI API
|
|
34
|
+
|
|
35
|
+
Seed 00: steps=10, achievements=2, tool_calls=10, reward≈1.250
|
|
36
|
+
Seed 01: steps=10, achievements=1, tool_calls=10, reward≈0.750
|
|
37
|
+
Seed 02: steps=10, achievements=3, tool_calls=10, reward≈1.500
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
Summary
|
|
41
|
+
-------
|
|
42
|
+
{
|
|
43
|
+
"model": "gpt-5-nano",
|
|
44
|
+
"provider": "openai",
|
|
45
|
+
"episodes": 10,
|
|
46
|
+
"mean_steps": 9.8,
|
|
47
|
+
"mean_achievements": 2.1,
|
|
48
|
+
"total_tool_calls": 98,
|
|
49
|
+
"output_dir": "examples/qwen_vl/temp/gpt5nano_frames"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Frames saved in: examples/qwen_vl/temp/gpt5nano_frames/
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each episode saves PNG frames (64x64) showing what the VLM saw:
|
|
56
|
+
```
|
|
57
|
+
examples/qwen_vl/temp/gpt5nano_frames/
|
|
58
|
+
seed_0000/
|
|
59
|
+
step_000.png
|
|
60
|
+
step_001.png
|
|
61
|
+
step_002.png
|
|
62
|
+
...
|
|
63
|
+
seed_0001/
|
|
64
|
+
...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🎯 Full Pipeline: Data Collection → SFT → RL
|
|
70
|
+
|
|
71
|
+
### Step 1: Collect Vision Traces
|
|
72
|
+
|
|
73
|
+
Collect 100 episodes with gpt-5-nano (for teacher distillation):
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export OPENAI_API_KEY="sk-..."
|
|
77
|
+
|
|
78
|
+
uv run python examples/qwen_vl/collect_vision_traces.py \
|
|
79
|
+
--model gpt-5-nano \
|
|
80
|
+
--provider openai \
|
|
81
|
+
--episodes 100 \
|
|
82
|
+
--max-steps 50 \
|
|
83
|
+
--output-dir traces/gpt5nano_vision
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Output:**
|
|
87
|
+
- SQLite DB: `traces/gpt5nano_vision/rollouts.db`
|
|
88
|
+
- Contains multimodal traces with images
|
|
89
|
+
- ~5000 samples (100 episodes × ~50 steps)
|
|
90
|
+
|
|
91
|
+
**Timeline:** 30-60 minutes
|
|
92
|
+
**Cost:** ~$1-2 (OpenAI gpt-5-nano)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Step 2: Export to SFT JSONL
|
|
97
|
+
|
|
98
|
+
Convert SQLite traces to SFT training format:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
uv run python examples/qwen_vl/export_traces_to_sft.py \
|
|
102
|
+
--db-path traces/gpt5nano_vision/rollouts.db \
|
|
103
|
+
--output traces/gpt5nano_vision/sft_dataset.jsonl \
|
|
104
|
+
--min-steps 5
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Output:**
|
|
108
|
+
- JSONL file with OpenAI-format messages
|
|
109
|
+
- Each line: `{"messages": [...], "metadata": {...}}`
|
|
110
|
+
- Messages include base64-encoded images
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Step 3: Split Train/Val
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
uv run python examples/qwen_vl/split_sft_data.py \
|
|
118
|
+
--input traces/gpt5nano_vision/sft_dataset.jsonl \
|
|
119
|
+
--train-output traces/gpt5nano_vision/train.jsonl \
|
|
120
|
+
--val-output traces/gpt5nano_vision/val.jsonl \
|
|
121
|
+
--val-fraction 0.1
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Output:**
|
|
125
|
+
- `train.jsonl`: ~4400 samples
|
|
126
|
+
- `val.jsonl`: ~500 samples
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### Step 4: Train Vision SFT
|
|
131
|
+
|
|
132
|
+
Use the example config or create your own:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
cd /path/to/monorepo
|
|
136
|
+
|
|
137
|
+
export BACKEND_BASE_URL="https://synth-backend-dev-docker.onrender.com/api"
|
|
138
|
+
|
|
139
|
+
uvx synth-ai train \
|
|
140
|
+
--type sft \
|
|
141
|
+
--config examples/qwen_vl/configs/crafter_vlm_sft_example.toml \
|
|
142
|
+
--env-file backend/.env.dev
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Hardware:** 2x H200 (or 4x H100)
|
|
146
|
+
**Time:** 2-4 hours (2 epochs)
|
|
147
|
+
**Cost:** ~$21 (Modal GPU pricing)
|
|
148
|
+
|
|
149
|
+
**Output:**
|
|
150
|
+
- LoRA adapter saved to HF Hub or S3
|
|
151
|
+
- Wandb logs with training curves
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### Step 5: Run Vision RL (Optional)
|
|
156
|
+
|
|
157
|
+
After SFT, fine-tune with GRPO for better performance:
|
|
158
|
+
|
|
159
|
+
```toml
|
|
160
|
+
# example RL config
|
|
161
|
+
[algorithm]
|
|
162
|
+
type = "online"
|
|
163
|
+
method = "grpo"
|
|
164
|
+
|
|
165
|
+
[model]
|
|
166
|
+
base = "Qwen/Qwen2-VL-7B-Instruct"
|
|
167
|
+
adapter = "s3://my-bucket/qwen2vl_crafter_sft" # From SFT
|
|
168
|
+
|
|
169
|
+
[compute]
|
|
170
|
+
gpu_count = 4 # 2 inference + 2 training
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Time:** 6-10 hours (20 iterations)
|
|
174
|
+
**Cost:** ~$112
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 📁 File Structure
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
synth-ai/examples/qwen_vl/
|
|
182
|
+
├── README.md # Overview
|
|
183
|
+
├── QUICKSTART.md # This file
|
|
184
|
+
├── __init__.py
|
|
185
|
+
│
|
|
186
|
+
├── crafter_gpt5nano_agent.py # OpenAI gpt-5-nano demo
|
|
187
|
+
├── crafter_qwen_vl_agent.py # Qwen-VL (synth-ai) demo
|
|
188
|
+
├── collect_vision_traces.py # Trace collection for SFT
|
|
189
|
+
├── run_vision_comparison.sh # Compare both models
|
|
190
|
+
│
|
|
191
|
+
├── configs/
|
|
192
|
+
│ └── crafter_vlm_sft_example.toml # Example SFT config
|
|
193
|
+
│
|
|
194
|
+
└── temp/ # Output frames and summaries
|
|
195
|
+
├── gpt5nano_frames/
|
|
196
|
+
├── qwen_vl_frames/
|
|
197
|
+
└── comparison/
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 🔍 How Vision Detection Works
|
|
203
|
+
|
|
204
|
+
CrafterPolicy automatically detects vision capability:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
# From examples/task_apps/crafter/.../policy.py
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _is_vision_model(model_name: str) -> bool:
|
|
210
|
+
"""Check if model supports vision from its name."""
|
|
211
|
+
model_lower = model_name.lower()
|
|
212
|
+
|
|
213
|
+
vision_patterns = [
|
|
214
|
+
"gpt-5", # ✅ gpt-5-nano, gpt-5-turbo, etc.
|
|
215
|
+
"gpt-4o", # ✅ gpt-4o-mini, gpt-4o
|
|
216
|
+
"qwen-vl", # ✅ Qwen-VL-Chat
|
|
217
|
+
"qwen2-vl", # ✅ Qwen2-VL-7B-Instruct
|
|
218
|
+
"qwen3-vl", # ✅ Qwen3-VL-8B
|
|
219
|
+
# ... more patterns
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
return any(pattern in model_lower for pattern in vision_patterns)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If detected:
|
|
226
|
+
- Policy includes base64 image in user message
|
|
227
|
+
- Images are 64x64 PNG frames from Crafter
|
|
228
|
+
- Format: `{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}`
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## 🎛️ Advanced Configuration
|
|
233
|
+
|
|
234
|
+
### Custom Image Resolution
|
|
235
|
+
|
|
236
|
+
Edit Crafter task instance config:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
instance.config = {
|
|
240
|
+
"seed": seed,
|
|
241
|
+
"length": 256,
|
|
242
|
+
"area": [128, 128], # Higher resolution (default: 64x64)
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
**Note:** Higher resolution = more tokens = higher cost
|
|
247
|
+
|
|
248
|
+
### Image-Only Mode
|
|
249
|
+
|
|
250
|
+
Disable text observations, use only images:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
await policy.initialize({
|
|
254
|
+
"use_tools": True,
|
|
255
|
+
"model": model,
|
|
256
|
+
"image_only_mode": True, # No text, only images
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Multiple Images per Step
|
|
261
|
+
|
|
262
|
+
For temporal context (not yet implemented):
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
# Future: Include last N frames
|
|
266
|
+
image_parts = [
|
|
267
|
+
{"type": "image_url", "image_url": {"url": frame_t}},
|
|
268
|
+
{"type": "image_url", "image_url": {"url": frame_t_minus_1}},
|
|
269
|
+
{"type": "image_url", "image_url": {"url": frame_t_minus_2}},
|
|
270
|
+
]
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## 🐛 Troubleshooting
|
|
276
|
+
|
|
277
|
+
### Error: `OPENAI_API_KEY not set`
|
|
278
|
+
```bash
|
|
279
|
+
export OPENAI_API_KEY="sk-..."
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Error: `SYNTH_API_KEY not set`
|
|
283
|
+
```bash
|
|
284
|
+
export SYNTH_API_KEY="sk_live_..."
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Error: `TracingStore not available`
|
|
288
|
+
Traces require synth-ai tracing module:
|
|
289
|
+
```bash
|
|
290
|
+
uv sync # Ensure all dependencies are installed
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Vision not detected
|
|
294
|
+
Manually enable:
|
|
295
|
+
```python
|
|
296
|
+
await policy.initialize({"use_vision": True})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## 📚 Related Documentation
|
|
302
|
+
|
|
303
|
+
- **SFT Pipeline:** See `/Users/joshpurtell/Documents/GitHub/monorepo/vision_sft_rl.txt` (Phase 9)
|
|
304
|
+
- **Crafter Environment:** `examples/task_apps/crafter/README.md`
|
|
305
|
+
- **OpenAI VLM Examples:** `examples/vlm/crafter_openai_vlm_agent.py`
|
|
306
|
+
- **Image-Only Eval:** `examples/task_apps/IMAGE_ONLY_EVAL_QUICKSTART.md`
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## 🎉 Next Steps
|
|
311
|
+
|
|
312
|
+
1. ✅ Run demos to verify vision inference works
|
|
313
|
+
2. 🎯 Collect training traces (100-1000 episodes)
|
|
314
|
+
3. 📦 Export and split into train/val
|
|
315
|
+
4. 🚀 Train VLM with LoRA (see `crafter_vlm_sft_example.toml`)
|
|
316
|
+
5. 🏆 Fine-tune with RL/GRPO for better achievement rates
|
|
317
|
+
6. 📊 Benchmark: base model vs SFT vs SFT+RL
|
|
318
|
+
|
|
319
|
+
**Expected Performance:**
|
|
320
|
+
- Base Qwen-VL: ~5-10% achievement rate
|
|
321
|
+
- After SFT (gpt-5-nano distillation): ~20-30%
|
|
322
|
+
- After RL (20 iterations): ~40-50%
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
Happy vision-language model training! 🚀✨
|
|
327
|
+
|