-
Notifications
You must be signed in to change notification settings - Fork 227
Add generation server scripts using HF accelerate and DS-inference #328
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 17 commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
47f3fc0
first step towards making libs
435af43
HF accelerate model
d1676fc
refactor accelerate
eef490c
refactor DS inference
25d0c70
refactor DS ZeRO
7be1410
make inference library
5c31d9a
cli
2905955
server
12c4cf7
request
46ade32
remove MaxTokensError
c46d957
fix batch size error with DS inference server
f3dac05
type fix
4425614
add latency
c97d6ea
add latency
f3385f2
add min_length to default kwargs
8f25200
str kwargs
b11bb7f
str kwargs
99dedb0
fix comma
497f00e
add old scripts back
aa8c08c
move scripts
25b5d85
drop data
9201770
minor changes + add README
649d7f8
update README
aa9ebea
Merge branch 'main' into add-generation-server
997a5fa
drop nccl
85d9fcb
fix
11d50f1
default values
403424b
resolve issues
493b2ee
handle keyboard interrupt
c84d9b7
remove caching
81d1469
use snapshot_download
a85b488
make server class
43a844c
fix snapshot download
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,189 @@ | ||
| import argparse | ||
| import gc | ||
| import os | ||
| import time | ||
| from typing import Any, List, Tuple, Union | ||
|
|
||
| import deepspeed | ||
| import torch | ||
|
|
||
| import constants | ||
| import utils | ||
| from ds_inference import DSInferenceModel | ||
| from ds_zero import DSZeROModel | ||
| from hf_accelerate import HFAccelerateModel | ||
| from utils import ( | ||
| Execute, | ||
| GenerateRequest, | ||
| Model, | ||
| get_argument_parser, | ||
| get_dummy_batch, | ||
| parse_generate_kwargs, | ||
| print_rank_n | ||
| ) | ||
|
|
||
|
|
||
| def run_and_log_time(execs: Union[List[Execute], Execute]) -> Tuple[Union[List[Any], Any], float]: | ||
| """ | ||
| runs a list of Execute objects and returns a list of outputs and the time taken | ||
| """ | ||
| start_time = time.time() | ||
|
|
||
| if (type(execs) == list): | ||
| results = [] | ||
| for e in execs: | ||
| results.append(e()) | ||
| else: | ||
| results = execs() | ||
|
|
||
| time_elapsed = time.time() - start_time | ||
| return results, time_elapsed | ||
|
|
||
|
|
||
| def benchmark_generation(model: Model, | ||
| request: GenerateRequest, | ||
| cycles: int = 5): | ||
| total_new_tokens_generated = 0 | ||
| for _ in range(cycles): | ||
| response = model.generate(request) | ||
| total_new_tokens_generated += sum( | ||
| new_tokens for new_tokens in response.num_generated_tokens) | ||
| return total_new_tokens_generated | ||
|
|
||
|
|
||
| def get_benchmark_results(benchmark_time: float, | ||
| initialization_time: float, | ||
| total_new_tokens_generated: int, | ||
| batch_size: int, | ||
| cycles: int) -> str: | ||
| throughput = total_new_tokens_generated / benchmark_time | ||
| latency = benchmark_time / cycles | ||
| return f""" | ||
| *** Performance stats: | ||
| Throughput (including tokenization) = {throughput:.2f} tokens/sec | ||
| Throughput (including tokenization) = {1000 / throughput:.2f} msecs/token | ||
| Model loading time = {initialization_time:.2f} secs | ||
| Total tokens generated = {total_new_tokens_generated} with batch size = {batch_size} | ||
| Latency = {latency:.2f} secs | ||
| Model loading time + generation time per batch = {initialization_time + latency:.2f} secs | ||
| """ | ||
|
|
||
|
|
||
| def benchmark_end_to_end(args: argparse.Namespace, | ||
| model_class: Model, | ||
| zero_activated: bool = False) -> None: | ||
| model, initialization_time = run_and_log_time( | ||
| Execute(model_class, {"args": args}) | ||
| ) | ||
|
|
||
| request = parse_generate_kwargs( | ||
| get_dummy_batch(args.batch_size), | ||
| args.generate_kwargs | ||
| ) | ||
|
|
||
| print_rank_n(f"generate_kwargs = {request}") | ||
| print_rank_n(f"batch_size = {args.batch_size}") | ||
|
|
||
| # warmup is a must if measuring speed as it's when all the optimizations are performed | ||
| # e.g. on 8x80 a100 the first pass of 100 tokens takes 23sec, and the next one is 4secs | ||
| response = model.generate(request) | ||
|
|
||
| for i, (o, _) in zip(request.text, zip(response.text, response.num_generated_tokens)): | ||
| print_rank_n(f"{'-' * 60}\nin = {i}\nout = {o}\n") | ||
|
|
||
| if (args.benchmark_cycles > 0): | ||
| print_rank_n(f"*** Running benchmark") | ||
|
|
||
| torch.cuda.empty_cache() | ||
| gc.collect() | ||
|
|
||
| # warm up | ||
| model.generate(request) | ||
| torch.cuda.synchronize() | ||
|
|
||
| # benchmark | ||
| total_new_tokens_generated, benchmark_time = run_and_log_time( | ||
| Execute( | ||
| benchmark_generation, | ||
| { | ||
| "model": model, | ||
| "request": request, | ||
| "cycles": args.benchmark_cycles | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| # with ZeRO every GPU is generating batch_size * sequence_length tokens | ||
| if (zero_activated): | ||
| world_size = int(os.getenv('WORLD_SIZE', '1')) | ||
| total_new_tokens_generated *= world_size | ||
|
|
||
| print_rank_n( | ||
| get_benchmark_results( | ||
| benchmark_time, | ||
| initialization_time, | ||
| total_new_tokens_generated, | ||
| args.batch_size, | ||
| args.benchmark_cycles | ||
| ) | ||
| ) | ||
|
|
||
|
|
||
| def get_args() -> argparse.Namespace: | ||
| parser = get_argument_parser() | ||
|
|
||
| group = parser.add_argument_group(title="launch config") | ||
| group.add_argument( | ||
| "--deployment_framework", | ||
| type=str, | ||
| choices=[ | ||
| constants.HF_ACCELERATE, | ||
| constants.DS_INFERENCE, | ||
| constants.DS_ZERO | ||
| ], | ||
| default=constants.HF_ACCELERATE | ||
| ) | ||
| group.add_argument("--benchmark_cycles", type=int, | ||
| default=0, help="additionally run benchmark") | ||
| group.add_argument("--local_rank", required=False, | ||
| type=int, help="used by dist launchers") | ||
| group.add_argument("--batch_size", default=1, type=int, help="batch size") | ||
| group.add_argument("--save_mp_checkpoint_path", required=False, | ||
| type=str, help="MP checkpoints path for DS inference") | ||
| group.add_argument("--cpu_offload", action="store_true", | ||
| help="whether to activate CPU offload for DS ZeRO") | ||
|
|
||
| args = utils.get_args(parser) | ||
|
|
||
| launched_with_deepspeed = args.deployment_framework in [ | ||
| constants.DS_INFERENCE, constants.DS_ZERO] | ||
|
|
||
| if (not launched_with_deepspeed): | ||
| assert args.local_rank == None, "local_rank must be None if not launched with DeepSpeed" | ||
|
|
||
| if (args.save_mp_checkpoint_path): | ||
| assert args.deployment_framework == constants.DS_INFERENCE, "save_mp_checkpoint_path only works with DS inference" | ||
|
|
||
| if (args.cpu_offload): | ||
| assert args.deployment_framework == constants.DS_ZERO, "cpu_offload only works with DS_ZeRO" | ||
|
|
||
| return args | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = get_args() | ||
|
|
||
| if (args.deployment_framework == constants.HF_ACCELERATE): | ||
| benchmark_end_to_end(args, HFAccelerateModel) | ||
| elif (args.deployment_framework == constants.DS_INFERENCE): | ||
| deepspeed.init_distributed("nccl") | ||
| benchmark_end_to_end(args, DSInferenceModel) | ||
| elif (args.deployment_framework == constants.DS_ZERO): | ||
| benchmark_end_to_end(args, DSZeROModel, zero_activated=True) | ||
| else: | ||
| raise ValueError( | ||
| f"Unknown deployment framework {args.deployment_framework}") | ||
|
|
||
|
|
||
| if (__name__ == "__main__"): | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import argparse | ||
|
|
||
| import utils | ||
| from ds_inference import cache_ds_checkpoints | ||
| from utils import get_argument_parser | ||
|
|
||
|
|
||
| def get_args() -> argparse.Namespace: | ||
| parser = get_argument_parser() | ||
|
|
||
| group = parser.add_argument_group(title="launch config") | ||
| group.add_argument("--local_rank", required=False, | ||
| type=int, help="used by dist launchers") | ||
| group.add_argument("--save_mp_checkpoint_path", required=True, | ||
| type=str, help="MP checkpoints path for DS inference") | ||
|
|
||
| args = utils.get_args(parser) | ||
|
|
||
| return args | ||
|
|
||
|
|
||
| def main() -> None: | ||
| cache_ds_checkpoints(get_args()) | ||
|
|
||
|
|
||
| if (__name__ == "__main__"): | ||
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| import argparse | ||
| import json | ||
|
|
||
| import constants | ||
| import utils | ||
| from ds_inference import DSInferenceGRPCServer | ||
| from hf_accelerate import HFAccelerateModel | ||
| from utils import get_argument_parser, parse_generate_kwargs, print_rank_n | ||
|
|
||
|
|
||
| def get_args() -> argparse.Namespace: | ||
| parser = get_argument_parser() | ||
|
|
||
| group = parser.add_argument_group(title="launch config") | ||
| group.add_argument( | ||
| "--deployment_framework", | ||
| type=str, | ||
| choices=[ | ||
| constants.HF_ACCELERATE, | ||
| constants.DS_INFERENCE | ||
| ], | ||
| default=constants.HF_ACCELERATE | ||
| ) | ||
| group.add_argument("--save_mp_checkpoint_path", required=False, | ||
| type=str, help="MP checkpoints path for DS inference") | ||
| group.add_argument("--shutdown_command", required=False, | ||
| type=str, default="__shutdown__", help="This string will exit the script") | ||
|
|
||
| args = utils.get_args(parser) | ||
|
|
||
| if (args.save_mp_checkpoint_path): | ||
| assert args.deployment_framework == constants.DS_INFERENCE, "save_mp_checkpoint_path only works with DS inference" | ||
|
|
||
| return args | ||
|
|
||
|
|
||
| def main() -> None: | ||
| args = get_args() | ||
|
|
||
| if (args.deployment_framework == constants.HF_ACCELERATE): | ||
| model = HFAccelerateModel(args) | ||
| elif (args.deployment_framework == constants.DS_INFERENCE): | ||
| model = DSInferenceGRPCServer(args) | ||
| else: | ||
| raise ValueError( | ||
| f"Unknown deployment framework {args.deployment_framework}") | ||
|
|
||
| generate_kwargs = args.generate_kwargs | ||
|
|
||
| while (True): | ||
| # currently only 1 process is running so its | ||
| # fine but might need to run_rank_n for this | ||
| # if running a deployment_framework with | ||
| # multiple processes | ||
| input_text = input("Input text: ") | ||
|
|
||
| if (input_text == args.shutdown_command): | ||
| model.shutdown() | ||
|
|
||
| if (input("change generate_kwargs? [y/n] ") == "y"): | ||
| generate_kwargs = json.loads(input("Generate kwargs: ")) | ||
|
|
||
| request = parse_generate_kwargs(input_text, generate_kwargs) | ||
| response = model.generate(request) | ||
|
|
||
| print_rank_n("Output text:", response.text) | ||
| print_rank_n("Generated tokens:", response.num_generated_tokens) | ||
|
|
||
|
|
||
| if (__name__ == "__main__"): | ||
| main() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| HF_ACCELERATE = "hf_accelerate" | ||
| DS_INFERENCE = "ds_inference" | ||
| DS_ZERO = "ds_zero" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| from .cache import cache_ds_checkpoints | ||
| from .grpc_server import DSInferenceGRPCServer | ||
| from .model import DSInferenceModel |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import argparse | ||
| import os | ||
| import shutil | ||
|
|
||
| import deepspeed | ||
| import torch | ||
| from transformers import AutoConfig, AutoModelForCausalLM | ||
|
|
||
| from utils import print_rank_n, run_rank_n | ||
|
|
||
| from .model import write_checkponts_json | ||
|
|
||
|
|
||
| def cache_ds_checkpoints(args: argparse.Namespace) -> None: | ||
| print_rank_n("Loading model...") | ||
| world_size = int(os.getenv("WORLD_SIZE", "1")) | ||
|
|
||
| # Load model | ||
| with deepspeed.OnDevice(dtype=args.dtype, device="meta"): | ||
| model = AutoModelForCausalLM.from_config( | ||
| AutoConfig.from_pretrained(args.model_name), | ||
| torch_dtype=torch.bfloat16 | ||
| ) | ||
| model = model.eval() | ||
|
|
||
| # Write checkpoints.json | ||
| tmp_directory = "tmp" | ||
| run_rank_n( | ||
| os.makedirs, | ||
| { | ||
| "name": tmp_directory, | ||
| "exist_ok": True | ||
| } | ||
| ) | ||
| checkpoints_json = os.path.join(tmp_directory, "checkpoints.json") | ||
| run_rank_n( | ||
| write_checkponts_json, | ||
| { | ||
| "checkpoints_json": checkpoints_json, | ||
| "model_name": args.model_name | ||
| }, | ||
| barrier=True | ||
| ) | ||
|
|
||
| run_rank_n( | ||
| os.makedirs, | ||
| { | ||
| "name": args.save_mp_checkpoint_path, | ||
| "exist_ok": True | ||
| }, | ||
| barrier=True | ||
| ) | ||
|
|
||
| if (args.dtype == torch.float16): | ||
| model = deepspeed.init_inference( | ||
| model, | ||
| mp_size=world_size, | ||
| dtype=args.dtype, | ||
| checkpoint=checkpoints_json, | ||
| replace_with_kernel_inject=True, | ||
| save_mp_checkpoint_path=args.save_mp_checkpoint_path | ||
| ) | ||
| elif (args.dtype == torch.bfloat16): | ||
| raise NotImplementedError("bfloat16 is not yet supported") | ||
|
|
||
| run_rank_n(shutil.rmtree, {"path": tmp_directory}) | ||
| print_rank_n("Model loaded") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.