You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

BatchedDecoding.cs 7.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. using System.Diagnostics;
  2. using System.Security.Cryptography;
  3. using System.Text;
  4. using LLama.Common;
  5. using LLama.Native;
  6. namespace LLama.Examples.NewVersion;
  7. public class BatchedDecoding
  8. {
  9. private const int n_parallel = 8;
  10. private const int n_len = 32;
  11. private const int top_k = 40;
  12. private const float top_p = 0.9f;
  13. private const float temp = 0.4f;
  14. public static async Task Run()
  15. {
  16. Console.Write("Please input your model path: ");
  17. //todo:var modelPath = Console.ReadLine();
  18. var modelPath = @"C:\Users\Martin\Documents\Python\oobabooga_windows\text-generation-webui\models\llama-2-7b-chat.Q5_K_M.gguf";
  19. Console.WriteLine("Prompt (leave blank to select automatically):");
  20. var prompt = Console.ReadLine();
  21. if (string.IsNullOrWhiteSpace(prompt))
  22. prompt = "I would like to tell you about";
  23. // Load model
  24. var parameters = new ModelParams(modelPath);
  25. using var model = LLamaWeights.LoadFromFile(parameters);
  26. // Tokenize prompt
  27. var prompt_tokens = model.NativeHandle.Tokenize(prompt, true, false, Encoding.UTF8);
  28. var n_kv_req = prompt_tokens.Length + (n_len - prompt_tokens.Length) * n_parallel;
  29. // Create a context
  30. parameters.ContextSize = (uint)model.ContextSize;
  31. parameters.Seed = unchecked((uint)RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue));
  32. parameters.BatchSize = (uint)Math.Max(n_len, n_parallel);
  33. using var context = model.CreateContext(parameters);
  34. var n_ctx = context.ContextSize;
  35. // make sure the KV cache is big enough to hold all the prompt and generated tokens
  36. if (n_kv_req > n_ctx)
  37. {
  38. await Console.Error.WriteLineAsync($"error: n_kv_req ({n_kv_req}) > n_ctx, the required KV cache size is not big enough\n");
  39. await Console.Error.WriteLineAsync(" either reduce n_parallel or increase n_ctx\n");
  40. return;
  41. }
  42. using var batch = LLamaBatchSafeHandle.Create(Math.Max(prompt_tokens.Length, n_parallel), 0, 1);
  43. // evaluate the initial prompt
  44. for (var i = 0; i < prompt_tokens.Length; i++)
  45. llama_batch_add(batch, prompt_tokens[i], i, new() { (LLamaSeqId)0 }, false);
  46. Debug.Assert(batch.NativeBatch.n_tokens == (int)prompt_tokens.Length);
  47. // llama_decode will output logits only for the last token of the prompt
  48. unsafe
  49. {
  50. batch.NativeBatch.logits[batch.NativeBatch.n_tokens - 1] = 1;
  51. }
  52. if (NativeApi.llama_decode(context.NativeHandle, batch.NativeBatch) != 0)
  53. {
  54. await Console.Error.WriteLineAsync("llama_decode failed");
  55. return;
  56. }
  57. // assign the system KV cache to all parallel sequences
  58. // this way, the parallel sequences will "reuse" the prompt tokens without having to copy them
  59. for (var i = 1; i < n_parallel; ++i)
  60. {
  61. NativeApi.llama_kv_cache_seq_cp(context.NativeHandle, (LLamaSeqId)0, (LLamaSeqId)i, 0, batch.NativeBatch.n_tokens);
  62. }
  63. if (n_parallel > 1)
  64. {
  65. Console.WriteLine();
  66. Console.WriteLine($"generating {n_parallel} sequences...");
  67. }
  68. // remember the batch index of the last token for each parallel sequence
  69. // we need this to determine which logits to sample from
  70. List<int> i_batch = new();
  71. for (var i = 0; i < n_parallel; i++)
  72. i_batch.Add(batch.NativeBatch.n_tokens - 1);
  73. int n_cur = batch.NativeBatch.n_tokens;
  74. int n_decode = 0;
  75. var streams = new List<int>[n_parallel];
  76. for (var i = 0; i < n_parallel; i++)
  77. streams[i] = new();
  78. var eos = model.EndOfSentenceToken;
  79. var nl = model.NewlineToken;
  80. var timer = new Stopwatch();
  81. timer.Start();
  82. while (n_cur <= n_len)
  83. {
  84. llama_batch_clear(batch);
  85. for (var i = 0; i < n_parallel; i++)
  86. {
  87. // Skip completed streams
  88. if (i_batch[i] < 0)
  89. continue;
  90. var n_vocab = model.VocabCount;
  91. LLamaTokenDataArray candidates;
  92. unsafe
  93. {
  94. candidates = LLamaTokenDataArray.Create(new Span<float>(NativeApi.llama_get_logits_ith(context.NativeHandle, i_batch[i]), n_vocab));
  95. }
  96. using var pin = LLamaTokenDataArrayNative.Create(candidates, out var candidates_native);
  97. candidates_native.TopK(context.NativeHandle, top_k);
  98. candidates_native.TopP(context.NativeHandle, top_p);
  99. candidates_native.Temperature(context.NativeHandle, temp);
  100. var new_token_id = candidates_native.SampleToken(context.NativeHandle);
  101. if (new_token_id == eos || new_token_id == nl)
  102. {
  103. i_batch[i] = -1;
  104. Console.WriteLine($"Completed Stream {i} early");
  105. continue;
  106. }
  107. streams[i].Add(new_token_id);
  108. i_batch[i] = batch.NativeBatch.n_tokens;
  109. // push this new token for next evaluation
  110. llama_batch_add(batch, new_token_id, n_cur, new() { (LLamaSeqId)i }, true);
  111. n_decode++;
  112. }
  113. // all streams are finished
  114. if (batch.NativeBatch.n_tokens == 0)
  115. {
  116. break;
  117. }
  118. n_cur++;
  119. // evaluate the current batch with the transformer model
  120. if (NativeApi.llama_decode(context.NativeHandle, batch.NativeBatch) != 0)
  121. {
  122. await Console.Error.WriteLineAsync("failed to eval");
  123. return;
  124. }
  125. }
  126. timer.Stop();
  127. Console.ForegroundColor = ConsoleColor.Yellow;
  128. Console.WriteLine();
  129. Console.WriteLine($"Decoded {n_decode} tokens in {timer.ElapsedMilliseconds}ms");
  130. Console.WriteLine($"Rate: {n_decode / timer.Elapsed.TotalSeconds:##.000} tokens/second");
  131. var index = 0;
  132. foreach (var stream in streams)
  133. {
  134. var text = context.DeTokenize(stream);
  135. Console.ForegroundColor = ConsoleColor.Green;
  136. Console.Write($"{index++}. {prompt}");
  137. Console.ForegroundColor = ConsoleColor.Red;
  138. Console.WriteLine(text);
  139. }
  140. }
  141. /// <summary>
  142. /// https://github.com/ggerganov/llama.cpp/blob/ad939626577cd25b462e8026cc543efb71528472/common/common.cpp#L829C2-L829C2
  143. /// </summary>
  144. private static void llama_batch_add(LLamaBatchSafeHandle batchHandle, int token, LLamaPos pos, List<LLamaSeqId> sequences, bool logits)
  145. {
  146. unsafe
  147. {
  148. ref var batch = ref batchHandle.NativeBatch;
  149. batch.token[batch.n_tokens] = token;
  150. batch.pos[batch.n_tokens] = pos;
  151. batch.n_seq_id[batch.n_tokens] = sequences.Count;
  152. for (var i = 0; i < sequences.Count; i++)
  153. batch.seq_id[batch.n_tokens][i] = sequences[i];
  154. batch.logits[batch.n_tokens] = Convert.ToByte(logits);
  155. batch.n_tokens++;
  156. }
  157. }
  158. /// <summary>
  159. /// https://github.com/ggerganov/llama.cpp/blob/ad939626577cd25b462e8026cc543efb71528472/common/common.cpp#L825
  160. /// </summary>
  161. /// <param name="batchHandle"></param>
  162. private static void llama_batch_clear(LLamaBatchSafeHandle batchHandle)
  163. {
  164. batchHandle.NativeBatch.n_tokens = 0;
  165. }
  166. }