Download run.py from rockmatrix-ai/EdgeRock: direct link, hf CLI and curl.
- Browser
- Download file 14.8 kB
-
https://huggingface.co/rockmatrix-ai/EdgeRock/resolve/main/run.py
- Command line
-
hf download hf://rockmatrix-ai/EdgeRock/run.py
-
curl -L -o run.py https://huggingface.co/rockmatrix-ai/EdgeRock/resolve/main/run.py
14.8 kB
| #!/usr/bin/env python3 | |
| import os | |
| from pathlib import Path | |
| import sys | |
| sys.dont_write_bytecode = True | |
| ROOT = Path(__file__).resolve().parent | |
| sys.path.insert(0, str(ROOT)) | |
| sys.path.insert(0, str(ROOT / 'runtime')) | |
| os.environ.pop('PYTHONPATH', None) | |
| import argparse | |
| import json | |
| import runpy | |
| import time | |
| from inference import ROOT, Engine, dump | |
| def benchmark(args): | |
| import torch | |
| engine = Engine(args.model, args.batch, args.profile, backend='cuda') | |
| groups = args.batch // args.k | |
| positions = torch.arange(args.prefill).unsqueeze(0) | |
| group_ids = torch.arange(groups).unsqueeze(1) | |
| unique = (positions * 104729 + 8191 + group_ids * 15485863).remainder(248320 - 4096) + 2048 | |
| ids = unique.repeat_interleave(args.k, dim=0).cuda() | |
| mask = torch.ones_like(ids) | |
| common = dict(slot_batch_size=args.batch, max_new_tokens=args.max_new_tokens, | |
| temperature=1.0, top_k=20, top_p=0.95, presence_penalty=1.5, | |
| eos_token_ids=(), pad_token_id=engine.tokenizer.pad_token_id, stop_check_interval_tokens=128, | |
| attempt_ids=torch.arange(args.batch, device='cuda'), | |
| prefill_group_ids=torch.arange(args.batch, device='cuda').div(args.k, rounding_mode='floor'), | |
| refill_batch_size=min(args.batch,16),refill_prefetch_size=min(args.batch,16), | |
| prefill_chunk_size=min(4 if args.prefill>=8192 else 16,max(1,32768//args.prefill)), | |
| prefill_token_chunk_size=16384, sampling_vocab_size=len(engine.tokenizer), | |
| counter_sampling_backend='triton_top20', adaptive_tail_compaction=False, | |
| mechanical_repetition_early_stop=False) | |
| with torch.inference_mode(): | |
| started=time.perf_counter() | |
| warm=engine.benchmark_generate(engine.model,ids,mask,**common,counter_seed=20260827, | |
| max_new_tokens_per_attempt=torch.full((args.batch,),args.warmup,device='cuda',dtype=torch.long)) | |
| cold=dict(seconds=time.perf_counter()-started,ttft_seconds=warm.metrics['initial_prefill_seconds']) | |
| del warm | |
| torch.cuda.empty_cache() | |
| torch.cuda.reset_peak_memory_stats() | |
| result=engine.benchmark_generate(engine.model,ids,mask,**common, | |
| counter_seed=20260828+args.prefill+(args.batch if args.batch>1 else 0)) | |
| metrics=result.metrics | |
| prefill=metrics['initial_prefill_seconds']+metrics['refill_prefill_seconds'] | |
| decode=metrics['generation_wall_seconds']-prefill | |
| receipt=dict(status='COMPLETE',batch=args.batch,prefill_tokens=args.prefill, | |
| generated_tokens=int(result.generated_lengths.sum()),decode_tokens=args.max_new_tokens, | |
| ttft_seconds=metrics['initial_prefill_seconds'],decode_seconds=decode, | |
| decode_tokens_per_second=metrics['decode_useful_token_events']/decode, | |
| prefill_unique_tokens_per_second=groups*args.prefill/prefill, | |
| end_to_end_output_tokens_per_second=int(result.generated_lengths.sum())/metrics['generation_wall_seconds'], | |
| peak_allocated_gib=torch.cuda.max_memory_allocated()/2**30, | |
| peak_reserved_gib=torch.cuda.max_memory_reserved()/2**30, | |
| warmup=cold,model_load_seconds=engine.load_seconds,operators=engine.operators, | |
| torch=torch.__version__,gpu=torch.cuda.get_device_name(),metrics=metrics) | |
| dump(args.output,receipt) | |
| print(json.dumps(receipt,ensure_ascii=False),flush=True) | |
| def serve(args): | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| from queue import Queue | |
| import threading | |
| import uuid | |
| from transformers import TextStreamer | |
| engine = Engine(args.model, args.batch, args.profile, backend=args.backend, device=args.device, dtype=args.dtype) | |
| warmup_started = time.perf_counter() | |
| if args.batch == 1: | |
| engine.generate([dict(prompt='Compute 17 × 23.', max_tokens=16)], cap=16, mechanical=False) | |
| lock = threading.Lock() | |
| class Handler(BaseHTTPRequestHandler): | |
| def send_json(self, value, status=200): | |
| body = json.dumps(value, ensure_ascii=False).encode() | |
| self.send_response(status) | |
| self.send_header('Content-Type', 'application/json') | |
| self.send_header('Content-Length', str(len(body))) | |
| self.end_headers() | |
| self.wfile.write(body) | |
| def do_GET(self): | |
| if self.path == '/v1/models': | |
| self.send_json(dict(object='list', data=[dict(id='EdgeRock-4B', object='model', owned_by='rockmatrix-ai')])) | |
| else: | |
| self.send_json(dict(status='READY', model='EdgeRock-4B', backend=engine.backend, batch_capacity=args.batch)) | |
| def do_POST(self): | |
| try: | |
| request = json.loads(self.rfile.read(int(self.headers['Content-Length']))) | |
| standard = self.path == '/v1/chat/completions' | |
| if self.path not in {'/', '/v1/chat/completions'}: | |
| self.send_json({'error': {'message': 'Unknown endpoint'}}, 404) | |
| return | |
| unsupported = [key for key in ['tools', 'tool_choice', 'response_format', 'stop'] if request.get(key)] | |
| if unsupported or request.get('n', 1) != 1: | |
| self.send_json({'error': {'message': 'This endpoint supports plain text chat with n=1.'}}, 400) | |
| return | |
| prompts = [{'messages': request['messages']}] if standard else request.get('prompts') or [{'messages': request['messages']}] | |
| settings = {key: request[key] for key in ['temperature', 'top_k', 'top_p', 'presence_penalty', 'seed'] if key in request} | |
| cap = int(request.get('max_completion_tokens', request.get('max_tokens', request.get('max_new_tokens', args.max_new_tokens)))) | |
| identity, created = 'chatcmpl-' + uuid.uuid4().hex, int(time.time()) | |
| def completion(rows): | |
| row = rows[0] | |
| text = engine.tokenizer.decode([t for t in row['response_ids'] if t not in (248044, 248046)], skip_special_tokens=False) | |
| usage = dict(prompt_tokens=row['prompt_tokens'], completion_tokens=row['generated_tokens'], total_tokens=row['prompt_tokens']+row['generated_tokens']) | |
| reason = 'length' if row['cap_hit'] else 'stop' | |
| return text, usage, reason | |
| if standard and request.get('stream'): | |
| queue = Queue() | |
| class TokenStream(TextStreamer): | |
| def on_finalized_text(self, text, stream_end=False): | |
| if text: | |
| queue.put(('text', text)) | |
| decoder = TokenStream(engine.tokenizer, skip_prompt=False, skip_special_tokens=False) | |
| def on_tokens(attempt, tokens): | |
| tokens = [t for t in tokens if t not in (248044, 248046)] | |
| if tokens: | |
| decoder.put(engine.torch.tensor(tokens)[None]) | |
| def generate(): | |
| try: | |
| with lock: | |
| rows, metrics = engine.generate(prompts, cap=cap, on_tokens=on_tokens, **settings) | |
| decoder.end() | |
| queue.put(('done', completion(rows))) | |
| except Exception as error: | |
| queue.put(('error', str(error))) | |
| threading.Thread(target=generate, daemon=True).start() | |
| self.send_response(200) | |
| self.send_header('Content-Type', 'text/event-stream') | |
| self.send_header('Cache-Control', 'no-cache') | |
| self.end_headers() | |
| def event(value): | |
| self.wfile.write(('data: ' + json.dumps(value, ensure_ascii=False) + '\n\n').encode()) | |
| self.wfile.flush() | |
| event(dict(id=identity, object='chat.completion.chunk', created=created, model='EdgeRock-4B', choices=[dict(index=0, delta={'role':'assistant'}, finish_reason=None)])) | |
| while True: | |
| kind, value = queue.get() | |
| if kind == 'error': | |
| event({'error': {'message': value}}) | |
| break | |
| if kind == 'text': | |
| event(dict(id=identity, object='chat.completion.chunk', created=created, model='EdgeRock-4B', choices=[dict(index=0, delta={'content':value}, finish_reason=None)])) | |
| else: | |
| text, usage, reason = value | |
| event(dict(id=identity, object='chat.completion.chunk', created=created, model='EdgeRock-4B', choices=[dict(index=0, delta={}, finish_reason=reason)])) | |
| if request.get('stream_options', {}).get('include_usage'): | |
| event(dict(id=identity, object='chat.completion.chunk', created=created, model='EdgeRock-4B', choices=[], usage=usage)) | |
| break | |
| self.wfile.write(b'data: [DONE]\n\n') | |
| self.wfile.flush() | |
| return | |
| with lock: | |
| rows, metrics = engine.generate(prompts, cap=cap, **settings) | |
| if standard: | |
| text, usage, reason = completion(rows) | |
| self.send_json(dict(id=identity, object='chat.completion', created=created, model='EdgeRock-4B', choices=[dict(index=0, message={'role':'assistant', 'content':text}, finish_reason=reason)], usage=usage)) | |
| else: | |
| self.send_json(dict(outputs=rows, metrics=metrics)) | |
| except (KeyError, TypeError, ValueError) as error: | |
| self.send_json({'error': {'message': str(error)}}, 400) | |
| except (BrokenPipeError, ConnectionResetError): | |
| return | |
| print(json.dumps(dict(status='READY', host=args.host, port=args.port, backend=engine.backend, | |
| batch=args.batch, warmup_seconds=time.perf_counter()-warmup_started)), flush=True) | |
| ThreadingHTTPServer((args.host, args.port), Handler).serve_forever() | |
| def main(): | |
| os.environ.setdefault('TOKENIZERS_PARALLELISM', 'false') | |
| os.environ.setdefault('NLTK_DATA', str(ROOT/'runtime/third_party/verifiers/nltk_data')) | |
| if len(sys.argv)>1 and sys.argv[1]=='_verify': | |
| kind=sys.argv[2] | |
| sys.argv=[sys.argv[0],*sys.argv[3:]] | |
| runpy.run_module('evaluation.'+{'math':'math_verifier','code':'code_verifier','instruction':'instruction_verifier'}[kind],run_name='__main__') | |
| return | |
| parser=argparse.ArgumentParser(description='EdgeRock-4B inference and reproducible evaluation') | |
| sub=parser.add_subparsers(dest='command',required=True) | |
| for name in ['demo','generate','serve','benchmark','evaluate','score','status','_worker']: | |
| p=sub.add_parser(name) | |
| p.add_argument('--model',type=Path,default=ROOT) | |
| p.add_argument('--output',type=Path,default=Path('outputs')/name) | |
| if name in {'demo','generate','serve','benchmark','evaluate','_worker'}: | |
| p.add_argument('--batch',type=int,default=1 if name in {'demo','serve'} else 320) | |
| p.add_argument('--backend',choices=['auto','torch','cuda'],default='auto') | |
| p.add_argument('--device') | |
| p.add_argument('--dtype',choices=['float32','float16','bfloat16']) | |
| p.add_argument('--profile',choices=['fast','reference'],default='fast') | |
| p.add_argument('--max-new-tokens',type=int,default=4096 if name in {'demo','benchmark'} else 131072) | |
| if name in {'demo','generate','evaluate','_worker'}: | |
| p.add_argument('--seed',type=int,default=20260923) | |
| if name=='demo': | |
| p.add_argument('--prompt',default='计算 17 × 23,并简短说明计算过程。') | |
| if name=='generate': | |
| p.add_argument('--input',type=Path,required=True) | |
| if name=='serve': | |
| p.add_argument('--host',default='127.0.0.1');p.add_argument('--port',type=int,default=8000) | |
| if name=='benchmark': | |
| p.add_argument('--prefill',type=int,default=4096) | |
| p.add_argument('--warmup',type=int,default=256) | |
| p.add_argument('--k',type=int,default=None) | |
| if name in {'evaluate','score'}: | |
| p.add_argument('--suite',choices=['text','memory','vision'],default='text') | |
| p.add_argument('--sources',nargs='*') | |
| p.add_argument('--cpu-workers',type=int,default=160) | |
| if name=='evaluate': | |
| p.add_argument('--gpus',default='0') | |
| p.add_argument('--attempts',type=Path,help='Rerun selected source/question/sample identities from a Parquet file.') | |
| p.add_argument('--limit-per-source',type=int) | |
| p.add_argument('--k',type=int,help='Override automatic K; such runs are reported separately.') | |
| p.add_argument('--generate-only',action='store_true') | |
| if name=='_worker': | |
| p.add_argument('--rank',type=int,required=True) | |
| p.add_argument('--port',type=int,required=True) | |
| p.add_argument('--suite',choices=['text','memory','vision'],default='text') | |
| args=parser.parse_args() | |
| if args.command=='benchmark': | |
| args.k = args.k if args.k is not None else min(8, args.batch) | |
| if min(args.batch, args.prefill, args.max_new_tokens, args.warmup, args.k) < 1: | |
| parser.error('benchmark batch, lengths, warmup, and k must be positive') | |
| if args.batch % args.k: | |
| parser.error('benchmark k must divide batch size') | |
| benchmark(args) | |
| elif args.command=='serve': | |
| serve(args) | |
| elif args.command in {'demo','generate'}: | |
| prompts=[args.prompt] if args.command=='demo' else [json.loads(line) for line in args.input.read_text().splitlines() if line.strip()] | |
| engine=Engine(args.model,args.batch,args.profile,backend=args.backend,device=args.device,dtype=args.dtype) | |
| rows,metrics=engine.generate(prompts,cap=args.max_new_tokens,seed=args.seed) | |
| args.output.mkdir(parents=True,exist_ok=True) | |
| (args.output/'responses.jsonl').write_text(''.join(json.dumps(r,ensure_ascii=False)+'\n' for r in rows)) | |
| dump(args.output/'metrics.json',metrics) | |
| for row in rows: | |
| print(row['response'],flush=True) | |
| print(json.dumps({'ttft_seconds':metrics['initial_prefill_seconds'],'decode_tokens_per_second':metrics['decode_tokens_per_second'],'output':str(args.output)},ensure_ascii=False)) | |
| elif args.command=='status': | |
| print((args.output/'STATUS.json').read_text()) | |
| else: | |
| from evaluate import evaluate,score,worker | |
| {'evaluate':evaluate,'score':score,'_worker':worker}[args.command](args) | |
| if __name__ == '__main__': | |
| main() | |