iRanadheer commited on
Commit
43df2d6
Β·
verified Β·
1 Parent(s): b4b6c25

Add model card

Browse files
Files changed (1) hide show
  1. README.md +236 -0
README.md ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ base_model:
6
+ - Qwen/Qwen3.6-27B
7
+ pipeline_tag: text-generation
8
+ library_name: transformers
9
+ tags:
10
+ - climate
11
+ - climate-change
12
+ - wind-energy
13
+ - climate-discourse
14
+ - opposition-detection
15
+ - classification
16
+ - qwen3
17
+ - fine-tuned
18
+ - cards
19
+ - joint
20
+ - quantized
21
+ - fp8
22
+ - image-text-to-text
23
+ - multimodal
24
+ - vision-language
25
+ datasets:
26
+ - C3DS/cards_sft_dataset
27
+ ---
28
+
29
+ # CARDS-Wind-Qwen3.6-27B-FP8
30
+
31
+ **Joint** climate-discourse classifier β€” a single Qwen3.6-27B backbone fine-tuned on **CARDS + Wind concatenated**, then FP8-dynamic quantized. One model handles both tasks:
32
+
33
+ - **CARDS** β€” classification of climate-contrarian claims under the [Coan et al. (2025)](https://doi.org/10.1038/s44458-025-00029-z) hierarchical taxonomy.
34
+ - **Wind** β€” three-level wind-energy opposition classification (detection / frames / claims).
35
+
36
+ The model picks the right task from the system prompt: pass the CARDS system prompt for CARDS-style output, the Wind system prompt for Wind-style output. Same weights, same chat template.
37
+
38
+ This is the FP8 deployment variant β€” ~27 GB on disk, fits on a single A100/H100/H200.
39
+
40
+ ## Why a joint model
41
+
42
+ - **One checkpoint, two tasks.** Saves disk + VRAM + ops complexity if you serve both classifiers in production.
43
+ - **Same recipe, additive cost.** Trained with `cards/ft/train.py --joint` β€” Unsloth + LoRA r=16 Ξ±=16, 3 epochs, lr=2e-4 β€” identical to the cards FT models, with the wind dataset added to the training mix.
44
+ - **Wind paper is forthcoming.** This model accompanies a forthcoming C3DS paper on wind-energy opposition discourse β€” research-preview status until the paper is out.
45
+
46
+ ## Results (Wind test set)
47
+
48
+ Evaluated on the wind-opposition test set (773 rows, 436 opposition-positive). Compared with the wind-only sibling, the BF16 joint model, and frontier APIs:
49
+
50
+ ### Detection (binary)
51
+
52
+ | Metric | Windy-27B FP8 | CARDS-Wind-27B (BF16) | **CARDS-Wind-27B (FP8 β€” this model)** | Claude Opus 4.7 | GPT-5.5 |
53
+ |---|---|---|---|---|---|
54
+ | Precision | 0.877 | 0.863 | **0.866** | 0.896 | **0.927** |
55
+ | Recall | 0.920 | 0.911 | **0.917** | 0.890 | 0.846 |
56
+ | F1 | **0.898** | 0.886 | **0.891** | 0.893 | 0.885 |
57
+
58
+ ### Samples F1 (multi-label frame / claim accuracy)
59
+
60
+ | View | Windy-27B FP8 | CARDS-Wind-27B (BF16) | **CARDS-Wind-27B (FP8)** | Claude Opus 4.7 | GPT-5.5 |
61
+ |---|---|---|---|---|---|
62
+ | Frames β€” all rows | 0.787 | 0.770 | **0.772** | 0.791 | 0.792 |
63
+ | Frames β€” opposition only | 0.751 | 0.736 | **0.739** | 0.734 | 0.697 |
64
+ | Claims β€” all rows | 0.755 | 0.733 | **0.738** | 0.754 | 0.745 |
65
+ | Claims β€” opposition only | 0.694 | 0.668 | **0.677** | 0.667 | 0.614 |
66
+
67
+ - **Joint training costs ~0.007 detection F1** vs the wind-only `Windy-27B-FP8` (0.891 vs 0.898) β€” small but real.
68
+ - Still **ties or beats Claude Opus 4.7 on detection F1** (0.891 vs 0.893) and beats it on frames-opposition-only and claims-opposition-only samples F1.
69
+ - **FP8 β‰ˆ BF16** on every metric β€” quantization is effectively free here.
70
+ - Zero parse failures on 773 test items.
71
+
72
+ ### CARDS test set
73
+
74
+ CARDS-side metrics for the joint model are **not separately reported** in this release β€” the cards-only sibling [`C3DS/CARDS-Qwen3.6-27B`](https://huggingface.co/C3DS/CARDS-Qwen3.6-27B) is the canonical reference for CARDS test-set numbers (samples F1 = 0.893 at L1, ties Opus 4.6). The joint model uses an identical CARDS training setup; expect comparable performance, but treat as approximate until reproduced.
75
+
76
+ ## Usage
77
+
78
+ ### Routing between the two tasks
79
+
80
+ The model picks its behaviour from the system prompt. Both prompts are bundled in this repo:
81
+
82
+ - [`cards_prompts.json`](./cards_prompts.json) β€” `slim_system_instruction` (CARDS) + `cot_trigger`
83
+ - [`wind_prompts.json`](./wind_prompts.json) β€” `slim_system_instruction` (Wind)
84
+
85
+ ### With vLLM
86
+
87
+ ```bash
88
+ vllm serve C3DS/CARDS-Wind-Qwen3.6-27B-FP8 \
89
+ --port 8000 \
90
+ --max-model-len 4096 \
91
+ --enable-prefix-caching \
92
+ --kv-cache-dtype fp8 \
93
+ --served-model-name CARDS-Wind-Qwen3.6-27B
94
+ ```
95
+
96
+ ```python
97
+ import json
98
+ from huggingface_hub import hf_hub_download
99
+ from openai import OpenAI
100
+
101
+ cards = json.load(open(hf_hub_download("C3DS/CARDS-Wind-Qwen3.6-27B-FP8", "cards_prompts.json")))
102
+ wind = json.load(open(hf_hub_download("C3DS/CARDS-Wind-Qwen3.6-27B-FP8", "wind_prompts.json")))
103
+
104
+ client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
105
+
106
+ def classify_cards(text):
107
+ resp = client.chat.completions.create(
108
+ model="CARDS-Wind-Qwen3.6-27B",
109
+ messages=[
110
+ {"role": "system", "content": cards["slim_system_instruction"]},
111
+ {"role": "user", "content": f"### Text:\n{text}\n\n{cards['cot_trigger']}"},
112
+ ],
113
+ temperature=0, max_tokens=4000,
114
+ )
115
+ return resp.choices[0].message.content
116
+
117
+ def classify_wind(text):
118
+ resp = client.chat.completions.create(
119
+ model="CARDS-Wind-Qwen3.6-27B",
120
+ messages=[
121
+ {"role": "system", "content": wind["slim_system_instruction"]},
122
+ {"role": "user", "content": text},
123
+ ],
124
+ temperature=0, max_tokens=4000,
125
+ )
126
+ return resp.choices[0].message.content
127
+ ```
128
+
129
+ Both modes produce a `<think>…</think>` reasoning trace followed by a YAML block. CARDS output is `categories: [...]`; Wind output is `opposition_detected`, `frames`, `claims`.
130
+
131
+
132
+ ### Multimodal β€” image + text
133
+
134
+ The base Qwen3.6-27B supports image inputs via the OpenAI-compatible
135
+ `image_url` content part, and this fine-tune preserves that capability for
136
+ both tasks. Switch tasks by switching the system prompt β€” CARDS prompt for
137
+ CARDS-style output, Wind prompt for Wind-style output β€” and pass an image
138
+ (with or without caption text) alongside.
139
+
140
+ Serve vLLM with multimodal flags enabled:
141
+
142
+ ```bash
143
+ vllm serve C3DS/CARDS-Wind-Qwen3.6-27B-FP8 \
144
+ --port 8000 \
145
+ --max-model-len 8192 \
146
+ --trust-remote-code \
147
+ --limit-mm-per-prompt image=4 \
148
+ --enable-prefix-caching \
149
+ --kv-cache-dtype fp8 \
150
+ --served-model-name CARDS-Wind-Qwen3.6-27B
151
+ ```
152
+
153
+ ```python
154
+ import base64, json, mimetypes
155
+ from pathlib import Path
156
+ from huggingface_hub import hf_hub_download
157
+ from openai import OpenAI
158
+
159
+ cards = json.load(open(hf_hub_download("C3DS/CARDS-Wind-Qwen3.6-27B-FP8", "cards_prompts.json")))
160
+ wind = json.load(open(hf_hub_download("C3DS/CARDS-Wind-Qwen3.6-27B-FP8", "wind_prompts.json")))
161
+
162
+ def image_part(path):
163
+ p = Path(path)
164
+ mime = mimetypes.guess_type(p)[0] or "image/png"
165
+ b64 = base64.b64encode(p.read_bytes()).decode()
166
+ return {"type": "image_url", "image_url": {"url": f"data:{mime};base64,{b64}"}}
167
+
168
+ client = OpenAI(base_url="http://localhost:8000/v1", api_key="dummy")
169
+
170
+ # Wind classification on an image:
171
+ resp = client.chat.completions.create(
172
+ model="CARDS-Wind-Qwen3.6-27B",
173
+ messages=[
174
+ {"role": "system", "content": wind["slim_system_instruction"]},
175
+ {"role": "user", "content": [
176
+ {"type": "text", "text": "Read the image and any caption below; classify the wind-opposition framing depicted."},
177
+ image_part("screenshot.png"),
178
+ {"type": "text", "text": "### Caption:\n<optional caption>"},
179
+ ]},
180
+ ],
181
+ temperature=0,
182
+ max_tokens=4000,
183
+ )
184
+ print(resp.choices[0].message.content)
185
+ ```
186
+
187
+ ## Training & Quantization
188
+
189
+ ### Joint fine-tuning
190
+
191
+ - **Base model:** `Qwen/Qwen3.6-27B`
192
+ - **Method:** LoRA (rank 16, Ξ± 16, dropout 0) on `q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj`, then merged into base weights
193
+ - **Datasets:** [`C3DS/cards_sft_dataset`](https://huggingface.co/datasets/C3DS/cards_sft_dataset) (CARDS RECoT messages) + the wind RECoT messages corpus (forthcoming release)
194
+ - **Mix:** datasets concatenated row-wise β€” no balancing or task-token; the model learns to pick its task from the system prompt
195
+ - **Framework:** Unsloth + TRL `SFTTrainer`, invoked via `cards/ft/train.py --joint`
196
+ - **Hyperparameters:** 3 epochs, `per_device_train_batch_size=1`, `gradient_accumulation_steps=8`, `lr=2e-4`, cosine schedule, 10 warmup steps, `max_seq_length=8192`, `adamw_8bit`, `bf16`
197
+
198
+ ### FP8 quantization
199
+
200
+ - **Scheme:** `fp8_e4m3` dynamic per-channel quantization (weights only). Activations stay in BF16; no calibration data required.
201
+ - **Targets:** linear layers in transformer blocks; `lm_head` left in BF16.
202
+ - **Tool:** `llmcompressor` with `QuantizationModifier(targets="Linear", scheme="FP8_DYNAMIC")` applied to the merged BF16 joint checkpoint.
203
+
204
+ ## Limitations
205
+
206
+ - **Forthcoming Wind paper.** Wind-side methodology, codebook, and dataset details are pending publication.
207
+ - **CARDS-side metrics not separately re-evaluated** for the joint model β€” refer to the cards-only sibling for the canonical CARDS test-set numbers.
208
+ - **Joint training trade-off.** Detection F1 on Wind is marginally lower than the wind-only `Windy-Qwen3.5-27B-FP8` (0.891 vs 0.898). Use the dedicated wind model if absolute wind detection F1 is the priority; use this one if you need both tasks from a single backbone.
209
+ - **Thinking tokens.** Training used `enable_thinking=True`. Parse output after `</think>` or disable thinking at inference.
210
+
211
+ ## Related models
212
+
213
+ - [`C3DS/CARDS-Qwen3.6-27B`](https://huggingface.co/C3DS/CARDS-Qwen3.6-27B) β€” CARDS-only Qwen3.6-27B sibling (same backbone, CARDS-only training).
214
+ - [`C3DS/Windy-Qwen3.5-27B-FP8`](https://huggingface.co/C3DS/Windy-Qwen3.5-27B-FP8) β€” Wind-only 27B FP8 sibling (Qwen3.5 backbone).
215
+
216
+ ## Citation
217
+
218
+ For the CARDS side of this model, please cite:
219
+
220
+ ```bibtex
221
+ @article{coan2025cards,
222
+ title = {Large language model reveals an increase in climate contrarian speech in the United States Congress},
223
+ author = {Coan, Travis G. and Malla, Ranadheer and Nanko, Mirjam O. and Kattrup, William and Roberts, J. Timmons and Cook, John and Boussalis, Constantine},
224
+ journal = {Communications Sustainability},
225
+ volume = {1},
226
+ pages = {37},
227
+ year = {2025},
228
+ doi = {10.1038/s44458-025-00029-z}
229
+ }
230
+ ```
231
+
232
+ A wind-side citation will be added when the corresponding paper is published.
233
+
234
+ ## License
235
+
236
+ Apache 2.0, inherited from Qwen3.6-27B.