okami.mom/albedo
git clone https://git.nkp.pet/okami.mom/albedo
6b24755
main
1- module ( albedo_openai_rate_bench ). 2- export ([ env / 2 , token / 0 , measure / 2 , trust / 1 ]). 3 4%% Helpers for manual/stream_rate_benchmark: streams against 5%% test/manual/mock_openai_server.py, each in its own process, measured 6%% together. 7 8env ( Name , Default ) -> 9case os : getenv ( binary_to_list ( Name )) of 10false -> Default ; 11Value -> unicode : characters_to_binary ( Value ) 12end . 13 14%% Trusts the CA in File for this run, for a mock serving https. 15trust (<<>>) -> nil ; 16trust ( File ) -> 17ok = public_key : cacerts_load ( File ), 18nil . 19 20%% Called from the stream callback for every text or argument delta. 21token () -> 22put ( arrivals , [ os : system_time ( nanosecond ) | get ( arrivals )]), 23nil . 24 25%% Runs every {StreamId, Run} at once, each in a fresh process; returns 26%% {Results, Metrics} where Metrics is a list of {Name, Number} pairs, per 27%% token across all streams. VM CPU is microstate accounting over every 28%% scheduler and thread, so the poller and the pool count too. 29measure ( Origin , Runs ) -> 30Owner = self (), 31erlang : garbage_collect (), 32{ Reductions0 , _ } = statistics ( reductions ), 33{ Runtime0 , _ } = statistics ( runtime ), 34msacc : start (), 35msacc : reset (), 36Start = erlang : monotonic_time ( microsecond ), 37Workers = [{ Id , spawn_monitor ( fun () -> 38put ( arrivals , []), 39Result = Run (), 40{ reductions , Reductions } = process_info ( self (), reductions ), 41{ memory , Memory } = process_info ( self (), memory ), 42Owner ! { self (), done , Result , Reductions , Memory , get ( arrivals )} 43end )} || { Id , Run } <- Runs ], 44Done = [ receive 45{ Pid , done , Result , Reductions , Memory , Arrivals } -> 46demonitor ( Ref , [ flush ]), 47{ Id , Result , Reductions , Memory , lists : reverse ( Arrivals )}; 48{ 'DOWN' , Ref , process , Pid , Reason } -> error ({ benchmark_worker_crashed , Reason }) 49end || { Id , { Pid , Ref }} <- Workers ], 50Elapsed = erlang : monotonic_time ( microsecond ) - Start , 51Stats = msacc : stats (), 52Runtime = msacc : stats ( system_runtime , Stats ), 53msacc : stop (), 54{ Runtime1 , _ } = statistics ( runtime ), 55case os : getenv ( "ALBEDO_BENCH_MSACC" ) of false -> ok ; _ -> msacc : ( Stats ) end , 56{ Reductions1 , _ } = statistics ( reductions ), 57Received = lists : sum ([ length ( Arrivals ) || { _ , _ , _ , _ , Arrivals } <- Done ]), 58Latency = latency ([{ timings ( Origin , Id ), Arrivals } || { Id , _ , _ , _ , Arrivals } <- Done ]), 59Per = fun ( X ) -> X / max ( 1 , Received ) end , 60Metrics = [ 61{<< "tokens" >>, Received }, 62{<< "wall_ms" >>, Elapsed / 1000 }, 63{<< "tok_per_s" >>, Received / ( Elapsed / 1000000 )}, 64{<< "vm_cpu_us_per_tok" >>, Per ( Runtime )}, 65{<< "vm_cpu_pct" >>, 100 * Runtime / Elapsed }, 66{<< "os_cpu_us_per_tok" >>, Per ( 1000 * ( Runtime1 - Runtime0 ))}, 67{<< "worker_reds_per_tok" >>, Per ( lists : sum ([ R || { _ , _ , R , _ , _ } <- Done ]))}, 68{<< "vm_reds_per_tok" >>, Per ( Reductions1 - Reductions0 )}, 69{<< "worker_memory_kb" >>, lists : max ([ M || { _ , _ , _ , M , _ } <- Done ]) / 1024 } 70| Latency 71], 72{[ Result || { _ , Result , _ , _ , _ } <- Done ], [{ Name , float ( Value )} || { Name , Value } <- Metrics ]}. 73 74%% Send-to-callback latency per token, from each stream's send times. 75latency ( Streams ) -> 76case lists : all ( fun ({ Sent , Arrivals }) -> length ( Sent ) =:= length ( Arrivals ) end , Streams ) of 77false -> [{<< "latency_mismatch" >>, 1 }]; 78true -> 79case lists : sort ([( A - S ) / 1000 || { Sent , Arrivals } <- Streams , 80{ S , A } <- lists : zip ( Sent , Arrivals )]) of 81[] -> []; 82Sorted -> 83[{<< "lat_p50_us" >>, pct ( Sorted , 0.50)}, {<< "lat_p99_us" >>, pct ( Sorted , 0.99)}, 84{<< "lat_max_us" >>, lists : last ( Sorted )}] 85end 86end . 87 88pct ( Sorted , P ) -> lists : nth ( max ( 1 , round ( P * length ( Sorted ))), Sorted ). 89 90timings ( Origin , Id ) -> 91# { scheme :=Scheme , host :=Host , port :=Port } = uri_string : parse ( Origin ), 92Options = [ binary , { active , false }], 93{ Module , { ok , Socket }} = case Scheme of 94<< "https" >> -> { ssl , ssl : connect ( binary_to_list ( Host ), Port , [{ verify , verify_none } | Options ])}; 95<< "http" >> -> { gen_tcp , gen_tcp : connect ( binary_to_list ( Host ), Port , Options )} 96end , 97ok = Module : send ( Socket , [<< "GET /timings/" >>, Id , << " HTTP/1.1\r\nhost: mock\r\n\r\n" >>]), 98Body = response_body ( Module , Socket , <<>>), 99Module : close ( Socket ), 100json : decode ( Body ). 101 102response_body ( Module , Socket , Read ) -> 103{ ok , Bytes } = Module : recv ( Socket , 0 , 5000 ), 104Response = << Read / binary , Bytes / binary >>, 105case binary : split ( Response , << "\r\n\r\n" >>) of 106[ Head , Body ] -> 107{ match , [ Length ]} = re : run ( Head , << "content-length: (\\d+)" >>, 108[ caseless , { capture , all_but_first , binary }]), 109case byte_size ( Body ) >= binary_to_integer ( Length ) of 110true -> Body ; 111false -> response_body ( Module , Socket , Response ) 112end ; 113[ _ ] -> response_body ( Module , Socket , Response ) 114end .