Text Classification
Transformers
Safetensors
English
llama
feature-extraction
llama3
reward-model
preference-modeling
rlhf
multi-domain
coherence
commonsense
empathy
multicultural
shared-prompt-gating
custom_code
text-embeddings-inference
Instructions to use mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it", trust_remote_code=True)# Load model directly from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it", trust_remote_code=True) model = AutoModel.from_pretrained("mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Release common-recipe model and update model card
Browse filesPublish the frozen GPU-trained shared-prompt model with verified files and synchronized documentation.
- README.md +30 -21
- config.json +1 -1
- model-00001-of-00004.safetensors +1 -1
- modeling_custom.py +45 -12
- requirements.txt +1 -1
- utils.py +143 -4
README.md
CHANGED
|
@@ -51,27 +51,27 @@ The model was trained with data from the
|
|
| 51 |
|
| 52 |
## Evaluation
|
| 53 |
|
| 54 |
-
Results on the
|
| 55 |
|
| 56 |
| Metric | Result |
|
| 57 |
| :--- | :---: |
|
| 58 |
-
| Test accuracy (%) | 86.
|
| 59 |
| Scoring Spearman | 0.7264 |
|
| 60 |
-
| Coherence accuracy |
|
| 61 |
-
| Commonsense accuracy | 97.
|
| 62 |
-
| Empathy accuracy |
|
| 63 |
-
| Multicultural accuracy | 74.
|
| 64 |
|
| 65 |
## Hugging Face Models
|
| 66 |
|
| 67 |
| Model | Base reward model | Test accuracy (%) | Scoring Spearman |
|
| 68 |
| :--- | :--- | :---: | :---: |
|
| 69 |
-
| [**`multi-domain-rm-fsfairx-gemma-2-9b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-fsfairx-gemma-2-9b-it) | [sfairXC/FsfairX-Gemma2-RM-v0.1](https://huggingface.co/sfairXC/FsfairX-Gemma2-RM-v0.1) | **88.
|
| 70 |
-
| [**`multi-domain-rm-skywork-qwen-3-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-skywork-qwen-3-8b-it) | [Skywork/Skywork-Reward-V2-Qwen3-8B](https://huggingface.co/Skywork/Skywork-Reward-V2-Qwen3-8B) | **
|
| 71 |
-
| [**`multi-domain-rm-fsfairx-llama-3-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-fsfairx-llama-3-8b-it) | [sfairXC/FsfairX-LLaMA3-RM-v0.1](https://huggingface.co/sfairXC/FsfairX-LLaMA3-RM-v0.1) | **
|
| 72 |
-
| [**`multi-domain-rm-skywork-llama-3.1-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it) | [Skywork/Skywork-Reward-V2-Llama-3.1-8B](https://huggingface.co/Skywork/Skywork-Reward-V2-Llama-3.1-8B) | **86.
|
| 73 |
-
| [**`multi-domain-rm-mistral-7b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-mistral-7b-it) | [weqweasdas/RM-Mistral-7B](https://huggingface.co/weqweasdas/RM-Mistral-7B) | **
|
| 74 |
-
| [**`multi-domain-rm-qwen-3-nemotron-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-qwen-3-nemotron-8b-it) | [nvidia/Qwen3-Nemotron-8B-BRRM](https://huggingface.co/nvidia/Qwen3-Nemotron-8B-BRRM) | **
|
| 75 |
|
| 76 |
## Usage
|
| 77 |
|
|
@@ -86,7 +86,7 @@ repo_id = "mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it"
|
|
| 86 |
tokenizer = AutoTokenizer.from_pretrained(repo_id, trust_remote_code=True)
|
| 87 |
model = AutoModel.from_pretrained(
|
| 88 |
repo_id,
|
| 89 |
-
|
| 90 |
device_map="auto",
|
| 91 |
trust_remote_code=True,
|
| 92 |
).eval()
|
|
@@ -98,41 +98,48 @@ chosen = prompt + [{
|
|
| 98 |
}]
|
| 99 |
rejected = prompt + [{"role": "assistant", "content": "Tell them to ignore it."}]
|
| 100 |
|
| 101 |
-
|
| 102 |
prompt,
|
| 103 |
tokenize=True,
|
| 104 |
add_generation_prompt=True,
|
| 105 |
return_tensors="pt",
|
|
|
|
| 106 |
).to(model.device)
|
| 107 |
-
|
| 108 |
chosen,
|
| 109 |
tokenize=True,
|
| 110 |
add_generation_prompt=False,
|
| 111 |
return_tensors="pt",
|
|
|
|
| 112 |
).to(model.device)
|
| 113 |
-
|
| 114 |
rejected,
|
| 115 |
tokenize=True,
|
| 116 |
add_generation_prompt=False,
|
| 117 |
return_tensors="pt",
|
|
|
|
| 118 |
).to(model.device)
|
| 119 |
|
| 120 |
with torch.inference_mode():
|
| 121 |
-
gate = model.compute_gating(
|
|
|
|
|
|
|
|
|
|
| 122 |
chosen_score = model(
|
| 123 |
-
input_ids=
|
|
|
|
| 124 |
gating_output_override=gate,
|
| 125 |
).score
|
| 126 |
rejected_score = model(
|
| 127 |
-
input_ids=
|
|
|
|
| 128 |
gating_output_override=gate,
|
| 129 |
).score
|
| 130 |
|
| 131 |
print({"chosen": chosen_score.item(), "rejected": rejected_score.item()})
|
| 132 |
```
|
| 133 |
|
| 134 |
-
|
| 135 |
-
within a prompt; they are not calibrated probabilities or universal utility values.
|
| 136 |
|
| 137 |
## Limitations
|
| 138 |
|
|
@@ -141,6 +148,8 @@ and should be calibrated for each downstream use case. Performance can vary by l
|
|
| 141 |
distribution. The model inherits limitations and biases from its base model and training data and
|
| 142 |
should not be used as the sole decision-maker in high-impact settings.
|
| 143 |
|
|
|
|
|
|
|
| 144 |
## Credits
|
| 145 |
|
| 146 |
This model is based on the ArmoRM/RLHFlow reward-modeling approach and adapts it to custom
|
|
|
|
| 51 |
|
| 52 |
## Evaluation
|
| 53 |
|
| 54 |
+
Results on the internal multi-domain test set:
|
| 55 |
|
| 56 |
| Metric | Result |
|
| 57 |
| :--- | :---: |
|
| 58 |
+
| Test accuracy (%) | 86.99 |
|
| 59 |
| Scoring Spearman | 0.7264 |
|
| 60 |
+
| Coherence accuracy | 76.32% |
|
| 61 |
+
| Commonsense accuracy | 97.33% |
|
| 62 |
+
| Empathy accuracy | 93.30% |
|
| 63 |
+
| Multicultural accuracy | 74.31% |
|
| 64 |
|
| 65 |
## Hugging Face Models
|
| 66 |
|
| 67 |
| Model | Base reward model | Test accuracy (%) | Scoring Spearman |
|
| 68 |
| :--- | :--- | :---: | :---: |
|
| 69 |
+
| [**`multi-domain-rm-fsfairx-gemma-2-9b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-fsfairx-gemma-2-9b-it) | [sfairXC/FsfairX-Gemma2-RM-v0.1](https://huggingface.co/sfairXC/FsfairX-Gemma2-RM-v0.1) | **88.80** | 0.7346 |
|
| 70 |
+
| [**`multi-domain-rm-skywork-qwen-3-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-skywork-qwen-3-8b-it) | [Skywork/Skywork-Reward-V2-Qwen3-8B](https://huggingface.co/Skywork/Skywork-Reward-V2-Qwen3-8B) | **88.08** | 0.7156 |
|
| 71 |
+
| [**`multi-domain-rm-fsfairx-llama-3-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-fsfairx-llama-3-8b-it) | [sfairXC/FsfairX-LLaMA3-RM-v0.1](https://huggingface.co/sfairXC/FsfairX-LLaMA3-RM-v0.1) | **87.75** | 0.7108 |
|
| 72 |
+
| [**`multi-domain-rm-skywork-llama-3.1-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-skywork-llama-3.1-8b-it) | [Skywork/Skywork-Reward-V2-Llama-3.1-8B](https://huggingface.co/Skywork/Skywork-Reward-V2-Llama-3.1-8B) | **86.99** | 0.7264 |
|
| 73 |
+
| [**`multi-domain-rm-mistral-7b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-mistral-7b-it) | [weqweasdas/RM-Mistral-7B](https://huggingface.co/weqweasdas/RM-Mistral-7B) | **85.25** | 0.6710 |
|
| 74 |
+
| [**`multi-domain-rm-qwen-3-nemotron-8b-it`**](https://huggingface.co/mario-rc/multi-domain-rm-qwen-3-nemotron-8b-it) | [nvidia/Qwen3-Nemotron-8B-BRRM](https://huggingface.co/nvidia/Qwen3-Nemotron-8B-BRRM) | **84.35** | 0.6704 |
|
| 75 |
|
| 76 |
## Usage
|
| 77 |
|
|
|
|
| 86 |
tokenizer = AutoTokenizer.from_pretrained(repo_id, trust_remote_code=True)
|
| 87 |
model = AutoModel.from_pretrained(
|
| 88 |
repo_id,
|
| 89 |
+
dtype=torch.bfloat16,
|
| 90 |
device_map="auto",
|
| 91 |
trust_remote_code=True,
|
| 92 |
).eval()
|
|
|
|
| 98 |
}]
|
| 99 |
rejected = prompt + [{"role": "assistant", "content": "Tell them to ignore it."}]
|
| 100 |
|
| 101 |
+
prompt_inputs = tokenizer.apply_chat_template(
|
| 102 |
prompt,
|
| 103 |
tokenize=True,
|
| 104 |
add_generation_prompt=True,
|
| 105 |
return_tensors="pt",
|
| 106 |
+
return_dict=True,
|
| 107 |
).to(model.device)
|
| 108 |
+
chosen_inputs = tokenizer.apply_chat_template(
|
| 109 |
chosen,
|
| 110 |
tokenize=True,
|
| 111 |
add_generation_prompt=False,
|
| 112 |
return_tensors="pt",
|
| 113 |
+
return_dict=True,
|
| 114 |
).to(model.device)
|
| 115 |
+
rejected_inputs = tokenizer.apply_chat_template(
|
| 116 |
rejected,
|
| 117 |
tokenize=True,
|
| 118 |
add_generation_prompt=False,
|
| 119 |
return_tensors="pt",
|
| 120 |
+
return_dict=True,
|
| 121 |
).to(model.device)
|
| 122 |
|
| 123 |
with torch.inference_mode():
|
| 124 |
+
gate = model.compute_gating(
|
| 125 |
+
input_ids=prompt_inputs["input_ids"],
|
| 126 |
+
attention_mask=prompt_inputs["attention_mask"],
|
| 127 |
+
)
|
| 128 |
chosen_score = model(
|
| 129 |
+
input_ids=chosen_inputs["input_ids"],
|
| 130 |
+
attention_mask=chosen_inputs["attention_mask"],
|
| 131 |
gating_output_override=gate,
|
| 132 |
).score
|
| 133 |
rejected_score = model(
|
| 134 |
+
input_ids=rejected_inputs["input_ids"],
|
| 135 |
+
attention_mask=rejected_inputs["attention_mask"],
|
| 136 |
gating_output_override=gate,
|
| 137 |
).score
|
| 138 |
|
| 139 |
print({"chosen": chosen_score.item(), "rejected": rejected_score.item()})
|
| 140 |
```
|
| 141 |
|
| 142 |
+
Pass the tokenizer's `input_ids` tensor and matching `attention_mask` to the model. Reuse the same prompt-derived gate for both candidates. Scores are intended for comparison within a prompt; they are not calibrated probabilities or universal utility values.
|
|
|
|
| 143 |
|
| 144 |
## Limitations
|
| 145 |
|
|
|
|
| 148 |
distribution. The model inherits limitations and biases from its base model and training data and
|
| 149 |
should not be used as the sole decision-maker in high-impact settings.
|
| 150 |
|
| 151 |
+
The internal test was examined during development, and a source audit identified some train–test prompt overlap. These results are not an independent confirmation of generalization.
|
| 152 |
+
|
| 153 |
## Credits
|
| 154 |
|
| 155 |
This model is based on the ArmoRM/RLHFlow reward-modeling approach and adapts it to custom
|
config.json
CHANGED
|
@@ -42,7 +42,7 @@
|
|
| 42 |
"gating_dropout": 0.05,
|
| 43 |
"gating_hidden_dim": 64,
|
| 44 |
"gating_learnable_logit_scale": false,
|
| 45 |
-
"gating_logit_scale":
|
| 46 |
"gating_n_hidden": 1,
|
| 47 |
"gating_temperature": 2.0,
|
| 48 |
"head_dim": 128,
|
|
|
|
| 42 |
"gating_dropout": 0.05,
|
| 43 |
"gating_hidden_dim": 64,
|
| 44 |
"gating_learnable_logit_scale": false,
|
| 45 |
+
"gating_logit_scale": 4.0,
|
| 46 |
"gating_n_hidden": 1,
|
| 47 |
"gating_temperature": 2.0,
|
| 48 |
"head_dim": 128,
|
model-00001-of-00004.safetensors
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
size 3903440936
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5aa0b32b04e4bd8ed6bd46cb09cec0212a8b532c2b7b0900d1bd49f3e6d82f81
|
| 3 |
size 3903440936
|
modeling_custom.py
CHANGED
|
@@ -18,6 +18,8 @@ class GatingNetwork(nn.Module):
|
|
| 18 |
learnable_logit_scale: bool = False,
|
| 19 |
active_attribute_indices: Optional[List[int]] = None):
|
| 20 |
super().__init__()
|
|
|
|
|
|
|
| 21 |
self.temperature = temperature
|
| 22 |
self.logit_scale = nn.Parameter(
|
| 23 |
torch.ones(1) * logit_scale, requires_grad=learnable_logit_scale
|
|
@@ -25,12 +27,18 @@ class GatingNetwork(nn.Module):
|
|
| 25 |
self.dropout_prob = dropout
|
| 26 |
active_mask = torch.ones(out_features, dtype=torch.bool)
|
| 27 |
if active_attribute_indices is not None:
|
| 28 |
-
if not active_attribute_indices
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
| 30 |
if min(active_attribute_indices) < 0 or max(active_attribute_indices) >= out_features:
|
| 31 |
raise ValueError("active_attribute_indices contains an out-of-range index.")
|
| 32 |
active_mask.zero_()
|
| 33 |
active_mask[list(active_attribute_indices)] = True
|
|
|
|
|
|
|
|
|
|
| 34 |
# Derived from packaged config; omit from state_dict for legacy compatibility.
|
| 35 |
self.register_buffer("active_attribute_mask", active_mask, persistent=False)
|
| 36 |
layers = []
|
|
@@ -40,6 +48,17 @@ class GatingNetwork(nn.Module):
|
|
| 40 |
layers.append(nn.Linear(in_features, out_features, bias=bias))
|
| 41 |
self.layers = nn.ModuleList(layers)
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 44 |
for i, layer in enumerate(self.layers):
|
| 45 |
x = layer(x)
|
|
@@ -68,24 +87,33 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 68 |
config_class = AutoConfig
|
| 69 |
base_model_prefix = "model"
|
| 70 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
def __init__(self, config):
|
| 72 |
super().__init__(config)
|
| 73 |
self.num_labels = config.num_labels
|
| 74 |
self.model = AutoModel.from_config(config)
|
| 75 |
config_dict = config.to_dict()
|
| 76 |
-
|
| 77 |
# Default objective count for this project.
|
| 78 |
self.num_objectives = config_dict.get("num_objectives", 23)
|
| 79 |
-
|
| 80 |
self.regression_layer = nn.Linear(config.hidden_size, self.num_objectives, bias=False)
|
| 81 |
self.post_init()
|
| 82 |
-
|
| 83 |
# Avoid torch.eye to keep compatibility with BF16 training setups.
|
| 84 |
I = torch.zeros(self.num_objectives, self.num_objectives)
|
| 85 |
I[range(self.num_objectives), range(self.num_objectives)] = 1.
|
| 86 |
self.reward_transform_matrix = nn.Parameter(I)
|
| 87 |
self.reward_transform_matrix.requires_grad = False
|
| 88 |
-
|
| 89 |
self.gating = GatingNetwork(config.hidden_size, self.num_objectives,
|
| 90 |
temperature=config_dict.get("gating_temperature", 10),
|
| 91 |
logit_scale=config_dict.get("gating_logit_scale", 1.0),
|
|
@@ -159,10 +187,10 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 159 |
batch_size = inputs_embeds.shape[0]
|
| 160 |
else:
|
| 161 |
raise ValueError("Either input_ids or inputs_embeds must be provided.")
|
| 162 |
-
|
| 163 |
if self.config.pad_token_id is None and batch_size != 1:
|
| 164 |
raise ValueError("Cannot handle batch sizes > 1 if no padding token is defined.")
|
| 165 |
-
|
| 166 |
if self.config.pad_token_id is None:
|
| 167 |
sequence_lengths = -1
|
| 168 |
else:
|
|
@@ -173,11 +201,11 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 173 |
sequence_lengths = sequence_lengths.to(tokens_hidden_states.device)
|
| 174 |
else:
|
| 175 |
sequence_lengths = -1
|
| 176 |
-
|
| 177 |
dummy_iterator = torch.arange(batch_size, device=tokens_hidden_states.device)
|
| 178 |
hidden_states = tokens_hidden_states[dummy_iterator, sequence_lengths]
|
| 179 |
assert hidden_states.shape == (batch_size, self.config.hidden_size)
|
| 180 |
-
|
| 181 |
rewards = self.regression_layer(hidden_states)
|
| 182 |
prompt_embedding = None
|
| 183 |
if gating_output_override is not None:
|
|
@@ -189,6 +217,11 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 189 |
f"gating_output_override shape {tuple(gating_output.shape)} does not "
|
| 190 |
f"match rewards shape {tuple(rewards.shape)}"
|
| 191 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
else:
|
| 193 |
if input_ids is None:
|
| 194 |
raise ValueError("input_ids is required to compute gating token positions.")
|
|
@@ -200,7 +233,7 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 200 |
gating_output = self.gating(prompt_embedding)
|
| 201 |
rewards_adjusted = rewards @ self.reward_transform_matrix
|
| 202 |
score = torch.sum(gating_output * rewards_adjusted, dim=1)
|
| 203 |
-
|
| 204 |
return CustomOutput(
|
| 205 |
rewards=rewards,
|
| 206 |
hidden_state=hidden_states,
|
|
@@ -212,4 +245,4 @@ class RewardModelWithGating(PreTrainedModel):
|
|
| 212 |
|
| 213 |
|
| 214 |
# Backward compatibility alias for existing imports/checkpoints.
|
| 215 |
-
LlamaForRewardModelWithGating = RewardModelWithGating
|
|
|
|
| 18 |
learnable_logit_scale: bool = False,
|
| 19 |
active_attribute_indices: Optional[List[int]] = None):
|
| 20 |
super().__init__()
|
| 21 |
+
if temperature <= 0:
|
| 22 |
+
raise ValueError("Temperature must be positive.")
|
| 23 |
self.temperature = temperature
|
| 24 |
self.logit_scale = nn.Parameter(
|
| 25 |
torch.ones(1) * logit_scale, requires_grad=learnable_logit_scale
|
|
|
|
| 27 |
self.dropout_prob = dropout
|
| 28 |
active_mask = torch.ones(out_features, dtype=torch.bool)
|
| 29 |
if active_attribute_indices is not None:
|
| 30 |
+
if (not isinstance(active_attribute_indices, (list, tuple))
|
| 31 |
+
or not active_attribute_indices
|
| 32 |
+
or any(type(index) is not int for index in active_attribute_indices)
|
| 33 |
+
or len(set(active_attribute_indices)) != len(active_attribute_indices)):
|
| 34 |
+
raise ValueError("Active attribute indices must be unique integers in a nonempty sequence.")
|
| 35 |
if min(active_attribute_indices) < 0 or max(active_attribute_indices) >= out_features:
|
| 36 |
raise ValueError("active_attribute_indices contains an out-of-range index.")
|
| 37 |
active_mask.zero_()
|
| 38 |
active_mask[list(active_attribute_indices)] = True
|
| 39 |
+
self.active_attribute_indices = tuple(
|
| 40 |
+
range(out_features) if active_attribute_indices is None else active_attribute_indices
|
| 41 |
+
)
|
| 42 |
# Derived from packaged config; omit from state_dict for legacy compatibility.
|
| 43 |
self.register_buffer("active_attribute_mask", active_mask, persistent=False)
|
| 44 |
layers = []
|
|
|
|
| 48 |
layers.append(nn.Linear(in_features, out_features, bias=bias))
|
| 49 |
self.layers = nn.ModuleList(layers)
|
| 50 |
|
| 51 |
+
@torch.no_grad()
|
| 52 |
+
def reset_active_attribute_mask(self):
|
| 53 |
+
"""Restore config-derived state after a low-memory/meta-device load.
|
| 54 |
+
|
| 55 |
+
Transformers can materialize non-persistent buffers with empty_like;
|
| 56 |
+
their constructor values therefore are not sufficient initialization.
|
| 57 |
+
This method changes no trained parameter or persistent checkpoint key.
|
| 58 |
+
"""
|
| 59 |
+
self.active_attribute_mask.zero_()
|
| 60 |
+
self.active_attribute_mask[list(self.active_attribute_indices)] = True
|
| 61 |
+
|
| 62 |
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 63 |
for i, layer in enumerate(self.layers):
|
| 64 |
x = layer(x)
|
|
|
|
| 87 |
config_class = AutoConfig
|
| 88 |
base_model_prefix = "model"
|
| 89 |
|
| 90 |
+
def _init_weights(self, module):
|
| 91 |
+
if isinstance(module, GatingNetwork):
|
| 92 |
+
# The non-persistent mask is absent from saved weights by design.
|
| 93 |
+
# Initialize it when the loader materializes missing/derived state,
|
| 94 |
+
# without reinitializing the gating parameters it already loaded.
|
| 95 |
+
module.reset_active_attribute_mask()
|
| 96 |
+
else:
|
| 97 |
+
super()._init_weights(module)
|
| 98 |
+
|
| 99 |
def __init__(self, config):
|
| 100 |
super().__init__(config)
|
| 101 |
self.num_labels = config.num_labels
|
| 102 |
self.model = AutoModel.from_config(config)
|
| 103 |
config_dict = config.to_dict()
|
| 104 |
+
|
| 105 |
# Default objective count for this project.
|
| 106 |
self.num_objectives = config_dict.get("num_objectives", 23)
|
| 107 |
+
|
| 108 |
self.regression_layer = nn.Linear(config.hidden_size, self.num_objectives, bias=False)
|
| 109 |
self.post_init()
|
| 110 |
+
|
| 111 |
# Avoid torch.eye to keep compatibility with BF16 training setups.
|
| 112 |
I = torch.zeros(self.num_objectives, self.num_objectives)
|
| 113 |
I[range(self.num_objectives), range(self.num_objectives)] = 1.
|
| 114 |
self.reward_transform_matrix = nn.Parameter(I)
|
| 115 |
self.reward_transform_matrix.requires_grad = False
|
| 116 |
+
|
| 117 |
self.gating = GatingNetwork(config.hidden_size, self.num_objectives,
|
| 118 |
temperature=config_dict.get("gating_temperature", 10),
|
| 119 |
logit_scale=config_dict.get("gating_logit_scale", 1.0),
|
|
|
|
| 187 |
batch_size = inputs_embeds.shape[0]
|
| 188 |
else:
|
| 189 |
raise ValueError("Either input_ids or inputs_embeds must be provided.")
|
| 190 |
+
|
| 191 |
if self.config.pad_token_id is None and batch_size != 1:
|
| 192 |
raise ValueError("Cannot handle batch sizes > 1 if no padding token is defined.")
|
| 193 |
+
|
| 194 |
if self.config.pad_token_id is None:
|
| 195 |
sequence_lengths = -1
|
| 196 |
else:
|
|
|
|
| 201 |
sequence_lengths = sequence_lengths.to(tokens_hidden_states.device)
|
| 202 |
else:
|
| 203 |
sequence_lengths = -1
|
| 204 |
+
|
| 205 |
dummy_iterator = torch.arange(batch_size, device=tokens_hidden_states.device)
|
| 206 |
hidden_states = tokens_hidden_states[dummy_iterator, sequence_lengths]
|
| 207 |
assert hidden_states.shape == (batch_size, self.config.hidden_size)
|
| 208 |
+
|
| 209 |
rewards = self.regression_layer(hidden_states)
|
| 210 |
prompt_embedding = None
|
| 211 |
if gating_output_override is not None:
|
|
|
|
| 217 |
f"gating_output_override shape {tuple(gating_output.shape)} does not "
|
| 218 |
f"match rewards shape {tuple(rewards.shape)}"
|
| 219 |
)
|
| 220 |
+
elif getattr(self.config, "shared_prompt_gating", False):
|
| 221 |
+
raise ValueError(
|
| 222 |
+
"This shared-prompt-gating checkpoint requires gating_output_override. "
|
| 223 |
+
"Compute one prompt-only gate with compute_gating() and reuse it for all candidates."
|
| 224 |
+
)
|
| 225 |
else:
|
| 226 |
if input_ids is None:
|
| 227 |
raise ValueError("input_ids is required to compute gating token positions.")
|
|
|
|
| 233 |
gating_output = self.gating(prompt_embedding)
|
| 234 |
rewards_adjusted = rewards @ self.reward_transform_matrix
|
| 235 |
score = torch.sum(gating_output * rewards_adjusted, dim=1)
|
| 236 |
+
|
| 237 |
return CustomOutput(
|
| 238 |
rewards=rewards,
|
| 239 |
hidden_state=hidden_states,
|
|
|
|
| 245 |
|
| 246 |
|
| 247 |
# Backward compatibility alias for existing imports/checkpoints.
|
| 248 |
+
LlamaForRewardModelWithGating = RewardModelWithGating
|
requirements.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
torch>=2.6
|
| 2 |
-
transformers
|
| 3 |
safetensors>=0.5
|
| 4 |
accelerate>=1.0
|
|
|
|
| 1 |
torch>=2.6
|
| 2 |
+
transformers==5.3.0
|
| 3 |
safetensors>=0.5
|
| 4 |
accelerate>=1.0
|
utils.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
import json
|
| 4 |
import os
|
|
|
|
| 5 |
from typing import Optional, Sequence
|
| 6 |
|
| 7 |
import torch
|
|
@@ -18,6 +19,144 @@ def _requires_remote_code(model_path: str) -> bool:
|
|
| 18 |
return "qwen3" in model_path_l
|
| 19 |
|
| 20 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
# ---------------------------------------------------------------------------
|
| 22 |
# Tokenizer loading
|
| 23 |
# ---------------------------------------------------------------------------
|
|
@@ -159,10 +298,6 @@ def _resolve_inference_model_path(
|
|
| 159 |
if not isinstance(inference_cfg, dict):
|
| 160 |
inference_cfg = {}
|
| 161 |
|
| 162 |
-
explicit_model_path = inference_cfg.get("model_path")
|
| 163 |
-
if explicit_model_path:
|
| 164 |
-
return str(explicit_model_path)
|
| 165 |
-
|
| 166 |
if cli_model_parent_dir or cli_model_name:
|
| 167 |
model_parent_dir = str(cli_model_parent_dir or inference_cfg.get("model_parent_dir", "model"))
|
| 168 |
model_name = cli_model_name or inference_cfg.get("model_name")
|
|
@@ -170,6 +305,10 @@ def _resolve_inference_model_path(
|
|
| 170 |
raise ValueError("model_name must be provided via --model_name or config.yaml inference.model_name")
|
| 171 |
return os.path.join(model_parent_dir, str(model_name))
|
| 172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
model_name = inference_cfg.get("model_name")
|
| 174 |
if not model_name:
|
| 175 |
raise ValueError("model_name must be provided via --model_name or config.yaml inference.model_name")
|
|
|
|
| 2 |
|
| 3 |
import json
|
| 4 |
import os
|
| 5 |
+
import importlib.util
|
| 6 |
from typing import Optional, Sequence
|
| 7 |
|
| 8 |
import torch
|
|
|
|
| 19 |
return "qwen3" in model_path_l
|
| 20 |
|
| 21 |
|
| 22 |
+
def _attention_implementation(device: str) -> str | None:
|
| 23 |
+
"""Use FlashAttention on CUDA when installed; otherwise use Transformers defaults."""
|
| 24 |
+
if str(device).startswith("cuda") and importlib.util.find_spec("flash_attn") is not None:
|
| 25 |
+
return "flash_attention_2"
|
| 26 |
+
return None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _stable_int64_id(value) -> int:
|
| 30 |
+
"""Return a deterministic non-negative signed-int64 identifier."""
|
| 31 |
+
import hashlib
|
| 32 |
+
|
| 33 |
+
if not isinstance(value, str):
|
| 34 |
+
value = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
|
| 35 |
+
digest = hashlib.blake2b(value.encode("utf-8"), digest_size=8).digest()
|
| 36 |
+
return int.from_bytes(digest, "big", signed=False) & ((1 << 63) - 1)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def debiasing_checkpoint_suffix(debiasing_dims, corr_threshold: float) -> str:
|
| 40 |
+
"""Encode reward-transform settings in a stable checkpoint suffix."""
|
| 41 |
+
dims = sorted({int(dimension) for dimension in (debiasing_dims or ()) if int(dimension) >= 0})
|
| 42 |
+
if not dims:
|
| 43 |
+
return "_dbnone"
|
| 44 |
+
threshold = format(float(corr_threshold), ".12g").replace("-", "m").replace(".", "p")
|
| 45 |
+
dimension_text = "-".join(map(str, dims))
|
| 46 |
+
return f"_db{dimension_text}_ct{threshold}"
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def validate_shared_routing_config(routing_config) -> dict:
|
| 50 |
+
"""Validate the metadata contract required by packaged shared-gate checkpoints."""
|
| 51 |
+
if not isinstance(routing_config, dict):
|
| 52 |
+
raise ValueError("Stage 2 checkpoint is missing its training_config mapping.")
|
| 53 |
+
if routing_config.get("format_version") != 2 or not routing_config.get("shared_prompt_gating", False):
|
| 54 |
+
raise ValueError(
|
| 55 |
+
"Stage 2 checkpoint must declare format_version=2 and "
|
| 56 |
+
"shared_prompt_gating=true; legacy checkpoints are not packageable."
|
| 57 |
+
)
|
| 58 |
+
return routing_config
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def score_shared_gate_candidates(
|
| 62 |
+
candidate_attribute_rewards: torch.Tensor,
|
| 63 |
+
shared_gate_weights: torch.Tensor,
|
| 64 |
+
) -> torch.Tensor:
|
| 65 |
+
"""Combine per-candidate attribute rewards with one gate per prompt pair."""
|
| 66 |
+
if candidate_attribute_rewards.ndim != 3 or shared_gate_weights.ndim != 2:
|
| 67 |
+
raise ValueError(
|
| 68 |
+
"Shared-gate scoring expects candidate rewards shaped "
|
| 69 |
+
"[pairs, candidates, attributes] and gate weights shaped "
|
| 70 |
+
"[pairs, attributes]."
|
| 71 |
+
)
|
| 72 |
+
if (
|
| 73 |
+
candidate_attribute_rewards.shape[0] != shared_gate_weights.shape[0]
|
| 74 |
+
or candidate_attribute_rewards.shape[-1] != shared_gate_weights.shape[-1]
|
| 75 |
+
):
|
| 76 |
+
raise ValueError(
|
| 77 |
+
"Candidate rewards and shared gate weights have incompatible "
|
| 78 |
+
"pair or attribute dimensions."
|
| 79 |
+
)
|
| 80 |
+
return torch.sum(
|
| 81 |
+
candidate_attribute_rewards * shared_gate_weights.unsqueeze(1), dim=-1
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def shared_gate_checkpoint_filename(args, model_name: str, preference_name: str, reference_name: str) -> str:
|
| 86 |
+
"""Build the canonical Shared-Gate V2 checkpoint filename."""
|
| 87 |
+
from attributes import attribute_selection_suffix
|
| 88 |
+
|
| 89 |
+
defaults = {
|
| 90 |
+
"learning_rate": 0.0005, "weight_decay": 0.0, "n_hidden": 1,
|
| 91 |
+
"hidden_size": 64, "dropout": 0.1, "batch_size": 2048,
|
| 92 |
+
"logit_scale": 2.0, "domain_loss_weight": 0.25,
|
| 93 |
+
"entropy_weight": 0.02, "load_balance_weight": 0.05,
|
| 94 |
+
}
|
| 95 |
+
hyperparameters = "".join(
|
| 96 |
+
f"_{key[:2]}{getattr(args, key, default)}"
|
| 97 |
+
for key, default in defaults.items()
|
| 98 |
+
)
|
| 99 |
+
debiasing_dims = (
|
| 100 |
+
[-1] if str(reference_name).lower() == "null"
|
| 101 |
+
else getattr(args, "debiasing_dims", [-1])
|
| 102 |
+
)
|
| 103 |
+
suffix = debiasing_checkpoint_suffix(
|
| 104 |
+
debiasing_dims, getattr(args, "corr_threshold", 0.04)
|
| 105 |
+
)
|
| 106 |
+
suffix += "_cv" if getattr(args, "curriculum", False) else ""
|
| 107 |
+
suffix += "_bd" if getattr(args, "balance_difficulties", False) else ""
|
| 108 |
+
suffix += "" if getattr(args, "balance_domains", True) else "_ubd"
|
| 109 |
+
suffix += "_lgs" if getattr(args, "learnable_logit_scale", False) else ""
|
| 110 |
+
entropy_floor = getattr(args, "entropy_floor_fraction", 0.35)
|
| 111 |
+
suffix += "" if entropy_floor == 0.35 else f"_ef{entropy_floor}"
|
| 112 |
+
suffix += attribute_selection_suffix(
|
| 113 |
+
getattr(args, "attribute_subset", "full"),
|
| 114 |
+
getattr(args, "exclude_attributes", []),
|
| 115 |
+
)
|
| 116 |
+
gate_input_mode = getattr(args, "gate_input_mode", "prompt")
|
| 117 |
+
gate_mode_codes = {
|
| 118 |
+
"prompt": "prompt",
|
| 119 |
+
"global": "global",
|
| 120 |
+
"shuffled_prompt": "shuffle",
|
| 121 |
+
"candidate_conditioned": "candidate",
|
| 122 |
+
}
|
| 123 |
+
if gate_input_mode not in gate_mode_codes:
|
| 124 |
+
raise ValueError(f"Unknown gate_input_mode: {gate_input_mode}")
|
| 125 |
+
if gate_input_mode != "prompt":
|
| 126 |
+
suffix += f"_gim-{gate_mode_codes[gate_input_mode]}"
|
| 127 |
+
held_out_domain = getattr(args, "held_out_domain", None)
|
| 128 |
+
if held_out_domain:
|
| 129 |
+
suffix += f"_holdout-{held_out_domain}"
|
| 130 |
+
checkpoint_tag = getattr(args, "checkpoint_tag", None)
|
| 131 |
+
suffix += f"_tag-{checkpoint_tag}" if checkpoint_tag else ""
|
| 132 |
+
validation_manifest = getattr(args, "validation_group_ids_path", None)
|
| 133 |
+
if validation_manifest:
|
| 134 |
+
import hashlib
|
| 135 |
+
with open(validation_manifest, "rb") as stream:
|
| 136 |
+
suffix += "_vs-" + hashlib.sha256(stream.read()).hexdigest()[:16]
|
| 137 |
+
suffix += "_refit" if getattr(args, "train_on_all", False) else ""
|
| 138 |
+
filename = (
|
| 139 |
+
f"gating_network_sgv2_{model_name}_mo_{args.multi_objective_dataset_name}_"
|
| 140 |
+
f"pref_{preference_name}_ref_{reference_name}"
|
| 141 |
+
f"_t{getattr(args, 'temperature', 2.0):.1f}"
|
| 142 |
+
f"_n{getattr(args, 'n_steps', 30000)}"
|
| 143 |
+
f"_seed{getattr(args, 'seed', 0)}{hyperparameters}{suffix}.pt"
|
| 144 |
+
)
|
| 145 |
+
# Linux filesystems normally limit a single path component to 255 bytes.
|
| 146 |
+
# Keep short legacy names unchanged, but make long ablation names portable
|
| 147 |
+
# and collision resistant for both Stage 2 saving and Stage 3 lookup.
|
| 148 |
+
max_filename_bytes = 240
|
| 149 |
+
if len(filename.encode("utf-8")) > max_filename_bytes:
|
| 150 |
+
import hashlib
|
| 151 |
+
|
| 152 |
+
digest = hashlib.sha256(filename.encode("utf-8")).hexdigest()[:16]
|
| 153 |
+
extension = ".pt"
|
| 154 |
+
budget = max_filename_bytes - len(f"_h{digest}{extension}")
|
| 155 |
+
filename = f"{filename[:-len(extension)][:budget]}_h{digest}{extension}"
|
| 156 |
+
return filename
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
|
| 160 |
# ---------------------------------------------------------------------------
|
| 161 |
# Tokenizer loading
|
| 162 |
# ---------------------------------------------------------------------------
|
|
|
|
| 298 |
if not isinstance(inference_cfg, dict):
|
| 299 |
inference_cfg = {}
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
if cli_model_parent_dir or cli_model_name:
|
| 302 |
model_parent_dir = str(cli_model_parent_dir or inference_cfg.get("model_parent_dir", "model"))
|
| 303 |
model_name = cli_model_name or inference_cfg.get("model_name")
|
|
|
|
| 305 |
raise ValueError("model_name must be provided via --model_name or config.yaml inference.model_name")
|
| 306 |
return os.path.join(model_parent_dir, str(model_name))
|
| 307 |
|
| 308 |
+
explicit_model_path = inference_cfg.get("model_path")
|
| 309 |
+
if explicit_model_path:
|
| 310 |
+
return str(explicit_model_path)
|
| 311 |
+
|
| 312 |
model_name = inference_cfg.get("model_name")
|
| 313 |
if not model_name:
|
| 314 |
raise ValueError("model_name must be provided via --model_name or config.yaml inference.model_name")
|