Run one interactive turn. The transcript is tentatively extended with user * and assistant markers, then ds4_session_sync() decides whether this is a KV * continuation. If prompt processing fails, the transcript rolls back before * returning to the prompt. */
| 1080 | free(fresh_logits); |
| 1081 | return live_top_n > 0 && fresh_top_n > 0 && live_top[0].id == fresh_top[0].id ? 0 : 1; |
| 1082 | } |
| 1083 | |
| 1084 | static int run_perplexity_file(ds4_engine *engine, const cli_config *cfg) { |
| 1085 | char *text = read_prompt_file(cfg->gen.perplexity_file_path, true); |
| 1086 | ds4_tokens tokens = {0}; |
| 1087 | ds4_tokenize_text(engine, text, &tokens); |
| 1088 | free(text); |
| 1089 | |
| 1090 | /* Seed the graph with enough real context to stay on the normal Metal |
| 1091 | * prefill path; scoring starts immediately after this fixed prefix. */ |
| 1092 | const int prefix_len = 32; |
| 1093 | if (tokens.len <= prefix_len) { |
| 1094 | fprintf(stderr, "ds4: --perplexity-file needs more than %d tokens\n", prefix_len); |
| 1095 | ds4_tokens_free(&tokens); |
| 1096 | return 1; |
| 1097 | } |
| 1098 | |
| 1099 | int scored = tokens.len - prefix_len; |
| 1100 | if (cfg->gen.n_predict > 0 && scored > cfg->gen.n_predict) scored = cfg->gen.n_predict; |
| 1101 | if (scored > cfg->gen.ctx_size - prefix_len) scored = cfg->gen.ctx_size - prefix_len; |
| 1102 | if (scored <= 0) { |
| 1103 | fprintf(stderr, "ds4: context too small for perplexity scoring\n"); |
| 1104 | ds4_tokens_free(&tokens); |
| 1105 | return 1; |
| 1106 | } |
| 1107 | |
| 1108 | ds4_session *session = NULL; |
| 1109 | if (ds4_session_create(&session, engine, cfg->gen.ctx_size) != 0) { |
| 1110 | fprintf(stderr, "ds4: --perplexity-file requires a graph session backend\n"); |
| 1111 | ds4_tokens_free(&tokens); |
| 1112 | return 1; |
| 1113 | } |
| 1114 | if (cli_wait_distributed_route(cfg, session) != 0) { |
| 1115 | ds4_session_free(session); |
| 1116 | ds4_tokens_free(&tokens); |
| 1117 | return 1; |
| 1118 | } |
| 1119 | |
| 1120 | ds4_tokens prefix = {0}; |
| 1121 | for (int i = 0; i < prefix_len; i++) ds4_tokens_push(&prefix, tokens.v[i]); |
| 1122 | char err[160]; |
| 1123 | if (ds4_session_sync(session, &prefix, err, sizeof(err)) != 0) { |
| 1124 | fprintf(stderr, "ds4: perplexity initial token failed: %s\n", err); |
| 1125 | ds4_tokens_free(&prefix); |
| 1126 | ds4_session_free(session); |
| 1127 | ds4_tokens_free(&tokens); |
| 1128 | return 1; |
| 1129 | } |
| 1130 | ds4_tokens_free(&prefix); |
| 1131 | |
| 1132 | double nll = 0.0; |
| 1133 | for (int j = 0; j < scored; j++) { |
| 1134 | const int i = prefix_len + j; |
| 1135 | ds4_token_score score; |
| 1136 | if (!ds4_session_token_logprob(session, tokens.v[i], &score)) { |
| 1137 | fprintf(stderr, "ds4: failed to score token %d\n", i); |
| 1138 | ds4_session_free(session); |
| 1139 | ds4_tokens_free(&tokens); |
no test coverage detected