mirror of
https://github.com/wassname/ray.git
synced 2026-07-29 11:26:04 +08:00
[raysgd] Cleanup User API (#7384)
* Init fp16 * fp16 and schedulers * scheduler linking and fp16 * to fp16 * loss scaling and documentation * more documentation * add tests, refactor config * moredocs * more docs * fix logo, add test mode, add fp16 flag * fix tests * fix scheduler * fix apex * improve safety * fix tests * fix tests * remove pin memory default * rm * fix * Update doc/examples/doc_code/raysgd_torch_signatures.py * fix * migrate changes from other PR * ok thanks * pass * signatures * lint' * Update python/ray/experimental/sgd/pytorch/utils.py * Apply suggestions from code review Co-Authored-By: Edward Oakes <ed.nmi.oakes@gmail.com> * should address most comments * comments * fix this ci * first_pass * add overrides * override * fixing up operators * format * sgd * constants * rm * revert * save * failures * fixes * trainer * run test * operator * code * op * ok done * operator * sgd test fixes * ok * trainer * format * Apply suggestions from code review Co-Authored-By: Edward Oakes <ed.nmi.oakes@gmail.com> * Update doc/source/raysgd/raysgd_pytorch.rst * docstring * dcgan * doc * commits * nit * testing * revert * Start renaming pytorch to torch * Rename PyTorchTrainer to TorchTrainer * Rename PyTorch runners to Torch runners * Finish renaming API * Rename to torch in tests * Finish renaming docs + tests * Run format + fix DeprecationWarning * fix * move tests up * benchmarks * rename * remove some args * better metrics output * fix up the benchmark * benchmark-yaml * horovod-benchmark * benchmarks * Remove benchmark code for cleanups * makedatacreator * relax * metrics * autosetsampler * profile * movements * OK * smoothen * fix * nitdocs * loss * comments * fix * fix * runner_tests * codes * example * fix_test * fix * tests Co-authored-by: Edward Oakes <ed.nmi.oakes@gmail.com> Co-authored-by: Maksim Smolin <maximsmol@gmail.com>
This commit is contained in:
co-authored by
Edward Oakes
Maksim Smolin
parent
89ec4adb72
commit
d192ef0611
@@ -1,9 +1,10 @@
|
||||
import collections
|
||||
import torch
|
||||
|
||||
from ray.util.sgd.utils import TimerStat, AverageMeter
|
||||
from ray.util.sgd.torch.constants import (
|
||||
SCHEDULER_STEP_EPOCH, SCHEDULER_STEP_BATCH, SCHEDULER_STEP, BATCH_COUNT)
|
||||
from ray.util.sgd.utils import (TimerCollection, AverageMeterCollection,
|
||||
NUM_SAMPLES)
|
||||
from ray.util.sgd.torch.constants import (SCHEDULER_STEP_EPOCH,
|
||||
SCHEDULER_STEP_BATCH, SCHEDULER_STEP)
|
||||
|
||||
amp = None
|
||||
|
||||
@@ -48,15 +49,10 @@ class TrainingOperator:
|
||||
config,
|
||||
models,
|
||||
optimizers,
|
||||
criterion,
|
||||
criterion=None,
|
||||
schedulers=None,
|
||||
use_fp16=False):
|
||||
# You are not expected to override this method.
|
||||
self.timers = {
|
||||
k: TimerStat()
|
||||
for k in ["fwd", "grad", "apply", "epoch_time"]
|
||||
}
|
||||
self._validated_customization = False
|
||||
self._models = models # List of models
|
||||
assert isinstance(models, collections.Iterable), (
|
||||
"Components need to be iterable. Got: {}".format(type(models)))
|
||||
@@ -80,9 +76,13 @@ class TrainingOperator:
|
||||
"Need to provide a custom operator subclassing "
|
||||
"TrainingOperator if using multi-scheduler, "
|
||||
"multi-model or multi-optimizer training/validation.")
|
||||
|
||||
self.timers = TimerCollection()
|
||||
self.setup(config)
|
||||
|
||||
def _set_timers(self, timers):
|
||||
"""Passes in the timers from the Runner."""
|
||||
self.timers = timers
|
||||
|
||||
def setup(self, config):
|
||||
"""Override this method to implement custom operator setup.
|
||||
|
||||
@@ -93,17 +93,35 @@ class TrainingOperator:
|
||||
pass
|
||||
|
||||
def train_epoch(self, iterator, info):
|
||||
"""Runs one standard training pass over the train_iterator.
|
||||
"""Runs one standard training pass over the training dataloader.
|
||||
|
||||
By default, this method will iterate over the given iterator and
|
||||
call ``self.train_batch`` over each batch.
|
||||
|
||||
If ``scheduler_step_freq`` is set, this class will also step the
|
||||
scheduler accordingly.
|
||||
call ``self.train_batch`` over each batch. If ``scheduler_step_freq``
|
||||
is set, this default method will also step the scheduler accordingly.
|
||||
|
||||
You do not need to call ``train_batch`` in this method if you plan
|
||||
to implement a custom optimization/training routine here.
|
||||
|
||||
You may find ``ray.util.sgd.utils.AverageMeterCollection`` useful
|
||||
when overriding this method. See example below:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def train_epoch(self, ...):
|
||||
meter_collection = AverageMeterCollection()
|
||||
self.model.train()
|
||||
for batch in iterator:
|
||||
# do some processing
|
||||
metrics = {"metric_1": 1, "metric_2": 3} # dict of metrics
|
||||
|
||||
# This keeps track of all metrics across multiple batches
|
||||
meter_collection.update(metrics, n=len(batch))
|
||||
|
||||
# Returns stats of the meters.
|
||||
stats = meter_collection.summary()
|
||||
return stats
|
||||
|
||||
|
||||
Args:
|
||||
iterator (iter): Iterator over the training data for the entire
|
||||
epoch. This iterator is expected to be entirely consumed.
|
||||
@@ -113,41 +131,28 @@ class TrainingOperator:
|
||||
Returns:
|
||||
A dict of metrics from training.
|
||||
"""
|
||||
self._losses = AverageMeter()
|
||||
metric_meters = AverageMeterCollection()
|
||||
|
||||
self.model.train()
|
||||
with self.timers["epoch_time"]:
|
||||
for batch_idx, batch in enumerate(iterator):
|
||||
batch_info = {
|
||||
"batch_idx": batch_idx,
|
||||
"global_step": self.global_step
|
||||
}
|
||||
batch_info.update(info)
|
||||
metrics = self.train_batch(batch, batch_info=batch_info)
|
||||
for batch_idx, batch in enumerate(iterator):
|
||||
batch_info = {
|
||||
"batch_idx": batch_idx,
|
||||
"global_step": self.global_step
|
||||
}
|
||||
batch_info.update(info)
|
||||
metrics = self.train_batch(batch, batch_info=batch_info)
|
||||
|
||||
if self.scheduler and batch_info.get(
|
||||
SCHEDULER_STEP) == SCHEDULER_STEP_BATCH:
|
||||
self.scheduler.step()
|
||||
if self.scheduler and batch_info.get(
|
||||
SCHEDULER_STEP) == SCHEDULER_STEP_BATCH:
|
||||
self.scheduler.step()
|
||||
|
||||
if "loss" in metrics:
|
||||
self._losses.update(
|
||||
metrics["loss"], n=metrics.get("num_samples", 1))
|
||||
self.global_step += 1
|
||||
metric_meters.update(metrics, n=metrics.pop(NUM_SAMPLES, 1))
|
||||
self.global_step += 1
|
||||
|
||||
if self.scheduler and info.get(SCHEDULER_STEP) == SCHEDULER_STEP_EPOCH:
|
||||
self.scheduler.step()
|
||||
|
||||
stats = {
|
||||
BATCH_COUNT: batch_idx + 1,
|
||||
"mean_train_loss": self._losses.avg,
|
||||
"last_train_loss": self._losses.val,
|
||||
"epoch_time": self.timers["epoch_time"].last
|
||||
}
|
||||
stats.update({
|
||||
timer_tag: timer.mean
|
||||
for timer_tag, timer in self.timers.items()
|
||||
})
|
||||
return stats
|
||||
return metric_meters.summary()
|
||||
|
||||
def train_batch(self, batch, batch_info):
|
||||
"""Computes loss and updates the model over one batch.
|
||||
@@ -176,6 +181,9 @@ class TrainingOperator:
|
||||
By default, this dictionary contains "loss" and "num_samples".
|
||||
"num_samples" corresponds to number of datapoints in the batch.
|
||||
However, you can provide any number of other values.
|
||||
Consider returning "num_samples" in the metrics because
|
||||
by default, ``train_epoch`` uses "num_samples" to
|
||||
calculate averages.
|
||||
|
||||
"""
|
||||
features, target = batch
|
||||
@@ -185,12 +193,12 @@ class TrainingOperator:
|
||||
target = target.cuda(non_blocking=True)
|
||||
|
||||
# Compute output.
|
||||
with self.timers["fwd"]:
|
||||
with self.timers.record("fwd"):
|
||||
output = self.model(features)
|
||||
loss = self.criterion(output, target)
|
||||
|
||||
# Compute gradients in a backward pass.
|
||||
with self.timers["grad"]:
|
||||
with self.timers.record("grad"):
|
||||
self.optimizer.zero_grad()
|
||||
if self.use_fp16:
|
||||
with amp.scale_loss(loss, self.optimizer) as scaled_loss:
|
||||
@@ -199,35 +207,34 @@ class TrainingOperator:
|
||||
loss.backward()
|
||||
|
||||
# Call step of optimizer to update model params.
|
||||
with self.timers["apply"]:
|
||||
with self.timers.record("apply"):
|
||||
self.optimizer.step()
|
||||
return {"loss": loss.item(), "num_samples": features.size(0)}
|
||||
return {"train_loss": loss.item(), NUM_SAMPLES: features.size(0)}
|
||||
|
||||
def validate(self, val_iterator, info):
|
||||
"""Runs one standard validation pass over the val_iterator.
|
||||
|
||||
This will call ``model.eval()`` and ``torch.no_grad`` when iterating
|
||||
over the validation dataset.
|
||||
over the validation dataloader.
|
||||
|
||||
If overriding this method, you can access model, criterion via
|
||||
``self.model`` and ``self.criterion``. You also do not need to call
|
||||
``validate_batch`` if overriding this method.
|
||||
|
||||
Args:
|
||||
val_iterator (iter): Iterable constructed over the
|
||||
validation dataset.
|
||||
val_iterator (iter): Iterable constructed from the
|
||||
validation dataloader.
|
||||
info: (dict): Dictionary for information to be used for custom
|
||||
validation operations.
|
||||
|
||||
Returns:
|
||||
A dict of metrics from the evaluation.
|
||||
By default, returns "mean_accuracy" and "mean_validation_loss"
|
||||
By default, returns "mean_accuracy" and "mean_val_loss"
|
||||
which is computed by aggregating "loss" and "correct" values
|
||||
from ``validate_batch`` and dividing it by the sum of
|
||||
``num_samples`` from all calls to ``self.validate_batch``.
|
||||
"""
|
||||
losses = AverageMeter()
|
||||
total_correct = 0
|
||||
metric_meters = AverageMeterCollection()
|
||||
|
||||
# switch to evaluate mode
|
||||
self.model.eval()
|
||||
@@ -236,19 +243,9 @@ class TrainingOperator:
|
||||
batch_info = {"batch_idx": batch_idx}
|
||||
batch_info.update(info)
|
||||
metrics = self.validate_batch(batch, batch_info)
|
||||
if "loss" in metrics:
|
||||
losses.update(
|
||||
metrics["loss"], n=metrics.get("num_samples", 1))
|
||||
metric_meters.update(metrics, n=metrics.pop(NUM_SAMPLES, 1))
|
||||
|
||||
if "num_correct" in metrics:
|
||||
total_correct += metrics["num_correct"]
|
||||
|
||||
stats = {
|
||||
"batch_count": batch_idx + 1,
|
||||
"mean_validation_loss": losses.avg,
|
||||
"mean_accuracy": total_correct / losses.count
|
||||
}
|
||||
return stats
|
||||
return metric_meters.summary()
|
||||
|
||||
def validate_batch(self, batch, batch_info):
|
||||
"""Calcuates the loss and accuracy over a given batch.
|
||||
@@ -262,7 +259,11 @@ class TrainingOperator:
|
||||
|
||||
Returns:
|
||||
A dict of metrics.
|
||||
By default, returns "loss", "num_correct", and "num_samples".
|
||||
By default, returns "val_loss", "val_accuracy", and
|
||||
"num_samples". When overriding, consider returning
|
||||
"num_samples" in the metrics because
|
||||
by default, ``validate`` uses "num_samples" to
|
||||
calculate averages.
|
||||
"""
|
||||
features, target = batch
|
||||
if torch.cuda.is_available():
|
||||
@@ -270,14 +271,18 @@ class TrainingOperator:
|
||||
target = target.cuda(non_blocking=True)
|
||||
|
||||
# compute output
|
||||
output = self.model(features)
|
||||
loss = self.criterion(output, target)
|
||||
_, predicted = torch.max(output.data, 1)
|
||||
|
||||
with self.timers.record("eval_fwd"):
|
||||
output = self.model(features)
|
||||
loss = self.criterion(output, target)
|
||||
_, predicted = torch.max(output.data, 1)
|
||||
|
||||
num_correct = (predicted == target).sum().item()
|
||||
num_samples = target.size(0)
|
||||
return {
|
||||
"loss": loss.item(),
|
||||
"num_correct": (predicted == target).sum().item(),
|
||||
"num_samples": target.size(0)
|
||||
"val_loss": loss.item(),
|
||||
"val_accuracy": num_correct / num_samples,
|
||||
NUM_SAMPLES: num_samples
|
||||
}
|
||||
|
||||
def state_dict(self):
|
||||
@@ -341,3 +346,24 @@ class _TestingOperator(TrainingOperator):
|
||||
if callable(func):
|
||||
return func(self, iterator, info)
|
||||
return {"done": 1}
|
||||
|
||||
|
||||
class _TestMetricsOperator(TrainingOperator):
|
||||
def setup(self, config):
|
||||
self._train_scores = config["scores"].copy()
|
||||
self._val_scores = config["val_scores"].copy()
|
||||
self.key = config["key"]
|
||||
|
||||
def train_batch(self, batch, batch_info=None):
|
||||
metrics = super(_TestMetricsOperator, self).train_batch(
|
||||
batch, batch_info)
|
||||
num_samples = metrics[NUM_SAMPLES]
|
||||
metrics.update({self.key: self._train_scores.pop(0) / num_samples})
|
||||
return metrics
|
||||
|
||||
def validate_batch(self, batch, batch_info=None):
|
||||
metrics = super(_TestMetricsOperator, self).validate_batch(
|
||||
batch, batch_info)
|
||||
num_samples = metrics[NUM_SAMPLES]
|
||||
metrics.update({self.key: self._val_scores.pop(0) / num_samples})
|
||||
return metrics
|
||||
|
||||
Reference in New Issue
Block a user