matrix.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. """Measured burst-serving matrix: exact input IDs, cold prefixes, decode window."""
  2. import concurrent.futures
  3. import hashlib
  4. import json
  5. import os
  6. from pathlib import Path
  7. import statistics
  8. import sys
  9. import time
  10. import urllib.request
  11. import uuid
  12. from tokenizers import Tokenizer
  13. model = Path(os.environ.get('MODEL_PATH','/models/DeepSeek-V4.1-Flash'))
  14. state = Path(os.environ.get('STATE_PATH','/state'))
  15. sys.path.insert(0,str(model/'encoding'))
  16. from encoding import encode_messages
  17. tokenizer = Tokenizer.from_file(str(model/'tokenizer.json'))
  18. manifest = Path(os.environ.get('MANIFEST_PATH',str(state/'launch.json')))
  19. identity = hashlib.sha256(manifest.read_bytes()).hexdigest()
  20. sizes = [int(x) for x in os.environ.get('PREFILL_SIZES','512,2048,8192,32768,65536,131072,200000,400000').split(',')]
  21. concurrencies = [int(x) for x in os.environ.get('CONCURRENCIES','1,2,4,8').split(',')]
  22. folder = state/('matrix-'+time.strftime('%Y%m%dT%H%M%S'))
  23. folder.mkdir(parents=True)
  24. headers = {'Content-Type':'application/json','Authorization':'Bearer '+(state/'api-key').read_text().strip()}
  25. def encode(text):
  26. return tokenizer.encode(text,add_special_tokens=False).ids
  27. filler = encode('Reference notes: the cache stores recently accessed entries. '
  28. 'An implementation should maintain ordering, handle replacement and validate its invariants.\n')
  29. instruction = ('\nNow write a complete Python LRU cache module with a doubly linked list and dictionary, '
  30. 'including get, put, delete, iteration, resize, clear, invariant validation, detailed docstrings '
  31. 'and ten usage examples. Return code only. Implement all methods fully.\n<|Assistant|></think>')
  32. suffix = encode(instruction)
  33. all_summaries = []
  34. def one(size,index,wave_start):
  35. nonce = uuid.uuid4().hex
  36. prefix = encode(encode_messages([dict(role='user',content=nonce+'\nRead these notes.\n')],
  37. thinking_mode='chat').split('<|Assistant|>')[0])
  38. room = size-len(prefix)-len(suffix)
  39. assert room >= 0
  40. ids = prefix+(filler*(room//len(filler)+1))[:room]+suffix
  41. assert len(ids) == size
  42. result = dict(index=index,prompt_tokens=size,nonce=nonce,manifest_sha256=identity,
  43. started_s=time.monotonic()-wave_start,events=[],output_budget=1024)
  44. try:
  45. req = urllib.request.Request('http://127.0.0.1:8010/generate',headers=headers,
  46. data=json.dumps(dict(input_ids=ids,stream=True,
  47. sampling_params={'temperature':0,'max_new_tokens':1024})).encode())
  48. with urllib.request.urlopen(req,timeout=2400) as response:
  49. previous = []
  50. for line in response:
  51. if not line.startswith(b'data: '): continue
  52. if line[6:].strip() == b'[DONE]': break
  53. event = json.loads(line[6:])
  54. current = event.get('output_ids',[])
  55. assert current[:len(previous)] == previous, 'Expected cumulative output IDs'
  56. previous = current
  57. result['events'].append(dict(seconds=time.monotonic()-wave_start,
  58. count=len(current),meta_info=event.get('meta_info',{})))
  59. result['output_ids'] = current
  60. result['text'] = event.get('text','')
  61. result['finished_s'] = time.monotonic()-wave_start
  62. def at(n):
  63. return next((e['seconds'] for e in result['events'] if e['count']>=n),None)
  64. result['first_token_s'] = at(1)
  65. result['ttft_s'] = at(1)-result['started_s']
  66. left,right = at(129),at(641)
  67. result['decode_window_tps'] = 512/(right-left) if left is not None and right is not None and right>left else None
  68. result['finish_reason'] = result['events'][-1]['meta_info'].get('finish_reason')
  69. except Exception as error:
  70. result['error'] = repr(error)
  71. if hasattr(error,'read'): result['error_body'] = error.read().decode()
  72. return result
  73. def render():
  74. lines = ['# Measured inference matrix','',
  75. 'Effective prefill includes queuing and mixed decode work until the last request reaches its first token. '
  76. 'Decode is the median per-request rate over emitted tokens 129–641, including serving stalls. '
  77. 'Concurrency is requested burst size; client overlap records how many streams had emitted tokens and remained unfinished. '
  78. 'Synthetic repeated notes, unique prefixes, 1,024-token output budget; this is not a quality test.','',
  79. '| Input tokens/request | Requested C | Effective prefill tok/s | Median TTFT s | Median decode tok/s/request | Peak client overlap | Success |',
  80. '|---:|---:|---:|---:|---:|---:|---:|']
  81. for r in all_summaries:
  82. def fmt(k): return f'{r[k]:.2f}' if r.get(k) is not None else '—'
  83. lines.append(f"| {r['input_tokens']} | {r['concurrency']} | {fmt('effective_prefill_tps')} | {fmt('median_ttft_s')} | {fmt('median_decode_tps')} | {r['peak_client_overlap']} | {r['successes']}/{r['concurrency']} |")
  84. (folder/'TABLE.md').write_text('\n'.join(lines)+'\n')
  85. print(str(folder),flush=True)
  86. for size in sizes:
  87. for concurrency in concurrencies:
  88. start = time.monotonic()
  89. with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as pool:
  90. records = list(pool.map(lambda i:one(size,i,start),range(concurrency)))
  91. (folder/f'{size}-c{concurrency}.json').write_text(json.dumps(records))
  92. good = [r for r in records if not r.get('error')]
  93. spans = sorted([(r['first_token_s'],1) for r in good]+[(r['finished_s'],-1) for r in good])
  94. active=peak=0
  95. for _,delta in spans:
  96. active+=delta
  97. peak=max(peak,active)
  98. rates = [r['decode_window_tps'] for r in good if r.get('decode_window_tps') is not None]
  99. summary = dict(input_tokens=size,concurrency=concurrency,successes=len(good),
  100. peak_client_overlap=peak,manifest_sha256=identity,
  101. effective_prefill_tps=size*len(good)/max(r['first_token_s'] for r in good) if good else None,
  102. median_ttft_s=statistics.median(r['ttft_s'] for r in good) if good else None,
  103. median_decode_tps=statistics.median(rates) if rates else None,
  104. errors=[r['error'] for r in records if r.get('error')])
  105. all_summaries.append(summary)
  106. (folder/'summary.json').write_text(json.dumps(all_summaries,indent=2))
  107. render()
  108. print(json.dumps(summary),flush=True)
  109. if len(good)!=concurrency:
  110. raise RuntimeError('Failed wave; inspect runtime before continuing')