summarize_from_feedback/sample.py (259 lines of code) (raw):
import json
import os
from typing import Optional
from dataclasses import dataclass, field
import numpy as np
import torch
import blobfile as bf
from summarize_from_feedback import task_data
from summarize_from_feedback.datasets import jsonl_encoding
from summarize_from_feedback.utils import exact_div, Timer, hyperparams
from summarize_from_feedback.utils.logging_utils import setup_logging_with_pacific_tz
from summarize_from_feedback.utils.torch_utils import label_logprobs
from summarize_from_feedback.utils.nested import map_nested
from summarize_from_feedback.utils.assertions import assert_shape_eq, assert_eq
from summarize_from_feedback.query_response_model import ModelSpec, SampleHParams, PADDING_TOKEN
from summarize_from_feedback.policy import Policy
from summarize_from_feedback.tasks import TaskHParams, ResponseEncoder
@dataclass
class HParams(hyperparams.HParams):
model_spec: ModelSpec = field(default_factory=ModelSpec)
orig_model_spec: Optional[ModelSpec] = None
task: TaskHParams = field(default_factory=TaskHParams)
query_dataset_split: str = None
sample: SampleHParams = field(default_factory=SampleHParams)
num_queries: int = None
# Note that these batch sizes are in # of datapoints; each datapoint may turn into multiple
# sequences fed to the model
queries_per_run_per_replica: int = 1
responses_per_query: int = 1
responses_per_query_per_batch: int = 1
seed: int = 0
fp16_activations: bool = True
INVALID_LOGPROB = 1.0
def avg_negative(x):
mask = x <= 0
return np.sum(x * mask) / np.sum(mask)
def main(H: HParams):
layout = H.model_spec.run_params.all_gpu_layout()
# Instantiate policy
policy = Policy(task_hparams=H.task, spec=H.model_spec, layout=layout)
if H.orig_model_spec:
assert H.orig_model_spec.run_params.n_shards == H.model_spec.run_params.n_shards
orig_policy = Policy(task_hparams=H.task, spec=H.orig_model_spec, layout=layout)
else:
orig_policy = None
encoder = policy.encoder
response_encoder = ResponseEncoder(H.task.response, encoder)
setup_logging_with_pacific_tz()
act_dtype = torch.float16 if H.fp16_activations else torch.float32
is_logging_rank = layout.is_logging_rank
total_queries_per_replica = exact_div(H.num_queries, layout.n_replicas)
num_runs = exact_div(total_queries_per_replica, H.queries_per_run_per_replica)
input_iter = task_data.get_iter_for_task(
H.task,
encoder=encoder,
dataset_split=H.query_dataset_split,
batch_size=H.queries_per_run_per_replica,
layout=layout,
seed=H.seed,
all_fields=True,
)
log_dir = os.getenv("OUTPUT_DIR") or os.path.join("/tmp/jobs", os.getenv("JOB_NAME"))
results_dir = os.path.join(log_dir, "results")
bf.makedirs(results_dir)
with open(os.path.join(log_dir, "hparams.json"), "w") as f:
json.dump(H.to_json(), f, indent=2)
if is_logging_rank:
with open(os.path.join(results_dir, "task_hparams.json"), "w") as f:
json.dump(H.task.to_json(), f)
with open(os.path.join(results_dir, "hparams.json"), "w") as f:
json.dump(H.to_json(), f)
# Creates files for printing. Only the replica root prints the files
local_file_name = os.devnull
if layout.is_replica_root:
fname = f"samples.{layout.replica_idx}.jsonl"
local_file_name = os.path.join(results_dir, fname)
print(f"Samples will be written to {local_file_name}")
def prepare_eval_fn_and_inputs(tokens):
def eval_fn(outputs_mb, eval_inputs_mb):
logprobs = label_logprobs(logits=outputs_mb["logits"], labels=eval_inputs_mb["labels"])
logprobs = torch.masked_fill(logprobs, eval_inputs_mb["mask"], INVALID_LOGPROB)
return dict(logprobs=logprobs)
mask = tokens == PADDING_TOKEN
return eval_fn, dict(labels=torch.masked_fill(tokens, mask, 0), mask=mask)
runs_per_query = exact_div(H.responses_per_query, H.responses_per_query_per_batch)
with open(local_file_name, "w") as f:
for run_idx in range(num_runs):
with Timer() as timer:
input = next(input_iter)
context_tokens = input["context"]["tokens"]
assert_shape_eq(
context_tokens,
(H.queries_per_run_per_replica, H.task.query.length),
"Context tokens shape mismatch",
)
ref_tokens = input["reference"]["tokens"].unsqueeze(1)
assert_shape_eq(
ref_tokens,
(H.queries_per_run_per_replica, 1, H.task.response.length),
"Ref tokens shape mismatch",
)
# Sample from policy
all_sample_results = []
for _ in range(runs_per_query):
sample_results = policy.sample(
context_tokens,
responses_per_query=H.responses_per_query_per_batch,
sample_H=H.sample,
act_dtype=act_dtype,
)
assert_shape_eq(
sample_results["samples"],
(
H.queries_per_run_per_replica,
H.responses_per_query_per_batch,
H.task.response.length,
),
"Samples size mismatch",
)
processed_samples = response_encoder.process_responses(
sample_results["samples"]
)
sample_results["processed_samples"] = processed_samples
assert_shape_eq(
processed_samples,
(
H.queries_per_run_per_replica,
H.responses_per_query_per_batch,
H.task.response.length,
),
"Samples size mismatch",
)
sample_results["logprobs"] = torch.masked_fill(
sample_results["logprobs"],
processed_samples == PADDING_TOKEN,
INVALID_LOGPROB,
)
if orig_policy is not None:
eval_fn, eval_inputs = prepare_eval_fn_and_inputs(processed_samples)
orig_eval_results = orig_policy.eval(
context_tokens,
processed_samples,
eval_fn=eval_fn,
eval_inputs=eval_inputs,
act_dtype=act_dtype,
)
sample_results["orig_eval_results"] = orig_eval_results
sample_results = map_nested(sample_results, lambda x: x.cpu().numpy())
all_sample_results.append(sample_results)
eval_fn, eval_inputs = prepare_eval_fn_and_inputs(ref_tokens)
ref_eval_results = policy.eval(
context_tokens,
ref_tokens,
eval_fn=eval_fn,
eval_inputs=eval_inputs,
act_dtype=act_dtype,
)
if orig_policy is not None:
orig_ref_eval_results = orig_policy.eval(
context_tokens,
ref_tokens,
eval_fn=eval_fn,
eval_inputs=eval_inputs,
act_dtype=act_dtype,
)
if layout.is_replica_root:
for batch_idx in range(H.queries_per_run_per_replica):
context_tokens = sample_results["contexts"][batch_idx]
context = encoder.decode(context_tokens)
# Dump to a file so that we can use things in downstream tasks
# samples (written to file) is now a list of strings
d = dict(context=context, context_tokens=context_tokens)
d["sample_tokens"] = np.concatenate(
[
sample_results["processed_samples"][batch_idx]
for sample_results in all_sample_results
],
axis=0,
)
assert_shape_eq(
d["sample_tokens"],
(H.responses_per_query, H.task.response.length),
"Sample tokens shape mismatch",
)
d["samples"] = response_encoder.decode_responses(d["sample_tokens"])
d["logprobs"] = np.concatenate(
[
sample_results["logprobs"][batch_idx]
for sample_results in all_sample_results
],
axis=0,
)
assert_shape_eq(
d["logprobs"],
(H.responses_per_query, H.task.response.length),
"Logprobs shape mismatch",
)
if orig_policy is not None:
d["orig_logprobs"] = np.concatenate(
[
sample_results["orig_eval_results"]["eval_stats"]["logprobs"][
batch_idx
]
for sample_results in all_sample_results
],
axis=0,
)
assert_shape_eq(
d["orig_logprobs"],
(H.responses_per_query, H.task.response.length),
"Orig logprobs shape mismatch",
)
# Process ref_tokens (H.queries_per_run_per_replica, H.task.response.length)
d["ref_tokens"] = ref_tokens[batch_idx].squeeze(0).cpu().numpy()
d["ref"] = response_encoder.decode_response(d["ref_tokens"])
assert_eq(
len(d["ref_tokens"]),
H.task.response.length,
"Ref tokens shape mismatch",
)
d["ref_logprobs"] = (
ref_eval_results["eval_stats"]["logprobs"][batch_idx]
.squeeze(0)
.cpu()
.numpy()
)
assert_eq(
len(d["ref_logprobs"]),
H.task.response.length,
"Ref logprobs shape mismatch",
)
if orig_policy is not None:
d["orig_ref_logprobs"] = (
orig_ref_eval_results["eval_stats"]["logprobs"][batch_idx]
.squeeze(0)
.cpu()
.numpy()
)
assert_eq(
len(d["orig_ref_logprobs"]),
H.task.response.length,
"Orig ref Logprobs shape mismatch",
)
if "extra_fields" in input:
d["extra_fields"] = input["extra_fields"][batch_idx]
print("=" * 80)
replica_sample_idx = run_idx * H.queries_per_run_per_replica + batch_idx
print(f"RESULT {replica_sample_idx} of {total_queries_per_replica}")
print(f"CONTEXT:")
print(context)
print(f"REF:")
print(d["ref"])
print("avg logprob", avg_negative(d["ref_logprobs"]))
if orig_policy is not None:
print("avg orig logprob", avg_negative(d["orig_ref_logprobs"]))
for sample_idx in range(H.responses_per_query):
print(f"SAMPLE {sample_idx}:")
print(d["samples"][sample_idx])
print("avg logprob", avg_negative(d["logprobs"][sample_idx]))
if orig_policy is not None:
print(
"avg orig logprob", avg_negative(d["orig_logprobs"][sample_idx])
)
f.write((json.dumps(jsonl_encoding.encode_example(d)) + "\n"))
if layout.is_replica_root:
print(f"Batch {run_idx+1} of {num_runs}. Took {timer.interval} seconds")
return dict(output_path=results_dir)