kazutab commited on
Commit
7b49da7
·
verified ·
1 Parent(s): 79b4c43

Upload folder using huggingface_hub (part 3)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. backend/llama.cpp/examples/model-conversion/scripts/causal/compare-embeddings-logits.sh +46 -0
  2. backend/llama.cpp/examples/model-conversion/scripts/causal/compare-logits.py +87 -0
  3. backend/llama.cpp/examples/model-conversion/scripts/causal/convert-model.sh +57 -0
  4. backend/llama.cpp/examples/model-conversion/scripts/causal/modelcard.template +13 -0
  5. backend/llama.cpp/examples/model-conversion/scripts/causal/run-casual-gen-embeddings-org.py +114 -0
  6. backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model-embeddings-logits.sh +23 -0
  7. backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model.sh +31 -0
  8. backend/llama.cpp/examples/model-conversion/scripts/causal/run-org-model.py +172 -0
  9. backend/llama.cpp/examples/model-conversion/scripts/embedding/compare-embeddings-logits.sh +84 -0
  10. backend/llama.cpp/examples/model-conversion/scripts/embedding/convert-model.sh +38 -0
  11. backend/llama.cpp/examples/model-conversion/scripts/embedding/modelcard.template +48 -0
  12. backend/llama.cpp/examples/model-conversion/scripts/embedding/run-converted-model.sh +55 -0
  13. backend/llama.cpp/examples/model-conversion/scripts/embedding/run-original-model.py +243 -0
  14. backend/llama.cpp/examples/model-conversion/scripts/utils/__init__.py +0 -0
  15. backend/llama.cpp/examples/model-conversion/scripts/utils/check-nmse.py +177 -0
  16. backend/llama.cpp/examples/model-conversion/scripts/utils/common.py +299 -0
  17. backend/llama.cpp/examples/model-conversion/scripts/utils/compare_tokens.py +76 -0
  18. backend/llama.cpp/examples/model-conversion/scripts/utils/create-collection-add-model.sh +8 -0
  19. backend/llama.cpp/examples/model-conversion/scripts/utils/curl-embedding-server.sh +6 -0
  20. backend/llama.cpp/examples/model-conversion/scripts/utils/hf-add-model-to-collection.py +80 -0
  21. backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-collection.py +106 -0
  22. backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-model.py +78 -0
  23. backend/llama.cpp/examples/model-conversion/scripts/utils/hf-upload-gguf-model.py +58 -0
  24. backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-converted-model.sh +14 -0
  25. backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-org-model.py +290 -0
  26. backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-gen.sh +40 -0
  27. backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run-simple.sh +32 -0
  28. backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run.sh +33 -0
  29. backend/llama.cpp/examples/model-conversion/scripts/utils/quantize.sh +53 -0
  30. backend/llama.cpp/examples/model-conversion/scripts/utils/run-embedding-server.sh +27 -0
  31. backend/llama.cpp/examples/model-conversion/scripts/utils/semantic_check.py +242 -0
  32. backend/llama.cpp/examples/parallel/CMakeLists.txt +5 -0
  33. backend/llama.cpp/examples/parallel/README.md +14 -0
  34. backend/llama.cpp/examples/parallel/parallel.cpp +520 -0
  35. backend/llama.cpp/examples/passkey/CMakeLists.txt +5 -0
  36. backend/llama.cpp/examples/passkey/README.md +15 -0
  37. backend/llama.cpp/examples/passkey/passkey.cpp +277 -0
  38. backend/llama.cpp/examples/pydantic_models_to_grammar.py +1322 -0
  39. backend/llama.cpp/examples/pydantic_models_to_grammar_examples.py +312 -0
  40. backend/llama.cpp/examples/reason-act.sh +16 -0
  41. backend/llama.cpp/examples/regex_to_grammar.py +20 -0
  42. backend/llama.cpp/examples/retrieval/CMakeLists.txt +5 -0
  43. backend/llama.cpp/examples/retrieval/README.md +69 -0
  44. backend/llama.cpp/examples/retrieval/retrieval.cpp +307 -0
  45. backend/llama.cpp/examples/server-llama2-13B.sh +26 -0
  46. backend/llama.cpp/examples/server_embd.py +35 -0
  47. backend/llama.cpp/examples/simple-chat/CMakeLists.txt +5 -0
  48. backend/llama.cpp/examples/simple-chat/README.md +7 -0
  49. backend/llama.cpp/examples/simple-chat/simple-chat.cpp +210 -0
  50. backend/llama.cpp/examples/simple-cmake-pkg/.gitignore +50 -0
backend/llama.cpp/examples/model-conversion/scripts/causal/compare-embeddings-logits.sh ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ MODEL_PATH="${1:-"$MODEL_PATH"}"
6
+ MODEL_NAME="${2:-$(basename "$MODEL_PATH")}"
7
+
8
+ CONVERTED_MODEL_PATH="${1:-"$CONVERTED_MODEL"}"
9
+ CONVERTED_MODEL_NAME="${2:-$(basename "$CONVERTED_MODEL_PATH" ".gguf")}"
10
+
11
+ if [ -t 0 ]; then
12
+ CPP_EMBEDDINGS="data/llamacpp-${CONVERTED_MODEL_NAME}-embeddings.bin"
13
+ else
14
+ # Process piped JSON data and convert to binary (matching logits.cpp format)
15
+ TEMP_FILE=$(mktemp /tmp/tmp.XXXXXX.binn)
16
+ python3 -c "
17
+ import json
18
+ import sys
19
+ import struct
20
+
21
+ data = json.load(sys.stdin)
22
+
23
+ # Flatten all embeddings completely
24
+ flattened = []
25
+ for item in data:
26
+ embedding = item['embedding']
27
+ for token_embedding in embedding:
28
+ flattened.extend(token_embedding)
29
+
30
+ print(f'Total embedding values: {len(flattened)}', file=sys.stderr)
31
+
32
+ # Write as binary floats - matches logitc.cpp fwrite format
33
+ with open('$TEMP_FILE', 'wb') as f:
34
+ for value in flattened:
35
+ f.write(struct.pack('f', value))
36
+ "
37
+ CPP_EMBEDDINGS="$TEMP_FILE"
38
+ trap "rm -f $TEMP_FILE" EXIT
39
+ fi
40
+
41
+ python scripts/utils/semantic_check.py --model-path $MODEL_PATH \
42
+ --python-embeddings data/pytorch-${MODEL_NAME}-embeddings.bin \
43
+ --cpp-embeddings $CPP_EMBEDDINGS \
44
+ --prompt "Hello world today" \
45
+ --causal
46
+
backend/llama.cpp/examples/model-conversion/scripts/causal/compare-logits.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import numpy as np
5
+ from pathlib import Path
6
+ import os
7
+
8
+ # Add utils directory to path for direct script execution
9
+ sys.path.insert(0, str(Path(__file__).parent.parent / "utils"))
10
+ from common import get_model_name_from_env_path, compare_tokens, exit_with_warning # type: ignore[import-not-found, ty:unresolved-import]
11
+
12
+ def quick_logits_check(pytorch_file, llamacpp_file):
13
+ """Lightweight sanity check before NMSE"""
14
+
15
+ try:
16
+ pytorch_logits = np.fromfile(pytorch_file, dtype=np.float32)
17
+ llamacpp_logits = np.fromfile(llamacpp_file, dtype=np.float32)
18
+ except Exception as e:
19
+ print(f"❌ NOK: Failed to load files - {e}")
20
+ return False
21
+
22
+ # Check shapes match
23
+ if pytorch_logits.shape != llamacpp_logits.shape:
24
+ print(f"❌ NOK: Shape mismatch - PyTorch: {pytorch_logits.shape}, llama.cpp: {llamacpp_logits.shape}")
25
+ return False
26
+
27
+ # Calculate key metrics
28
+ diff = pytorch_logits - llamacpp_logits
29
+ abs_diff = np.abs(diff)
30
+ max_diff = np.max(abs_diff)
31
+
32
+ # Get top 10 predictions from both models
33
+ pytorch_top10 = np.argsort(pytorch_logits)[-10:][::-1]
34
+ llamacpp_top10 = np.argsort(llamacpp_logits)[-10:][::-1]
35
+ print(f"Top 10 PyTorch logits: {pytorch_logits[pytorch_top10]}")
36
+ print(f"Top 10 llama.cpp logits: {llamacpp_logits[llamacpp_top10]}")
37
+ print(f"Max absolute difference: {max_diff:.4f}")
38
+
39
+ return True
40
+
41
+ def main():
42
+ model_path = os.environ.get('MODEL_PATH')
43
+ model_name = get_model_name_from_env_path('MODEL_PATH')
44
+ data_dir = Path("data")
45
+ pytorch_file = data_dir / f"pytorch-{model_name}.bin"
46
+
47
+ llamacpp_model_name = get_model_name_from_env_path('CONVERTED_MODEL')
48
+ print(f"Using converted model: {llamacpp_model_name}")
49
+ llamacpp_file = data_dir / f"llamacpp-{llamacpp_model_name}.bin"
50
+
51
+ if not pytorch_file.exists():
52
+ print(f"Error: PyTorch logits file not found: {pytorch_file}")
53
+ print("Please run scripts/run-org-model.sh first to generate this file.")
54
+ sys.exit(1)
55
+
56
+ if not llamacpp_file.exists():
57
+ print(f"Error: llama.cpp logits file not found: {llamacpp_file}")
58
+ print("Please run scripts/run-converted-model.sh first to generate this file.")
59
+ sys.exit(1)
60
+
61
+ print("Checked all required files were found. Proceeding...\n")
62
+
63
+ # Verify tokens as they are a prerequisite for logits comparison.
64
+ print("🔍 Token Comparison Check")
65
+ print("=" * 40)
66
+ if not compare_tokens(f"pytorch-{model_name}", f"llamacpp-{llamacpp_model_name}"):
67
+ exit_with_warning("\n❌ Token mismatch detected", model_path)
68
+ print()
69
+
70
+ print("🔍 GGML Model Validation for model ", model_name)
71
+ print("=" * 40)
72
+ print(f"PyTorch logits : {pytorch_file}")
73
+ print(f"llama.cpp logits: {llamacpp_file}")
74
+ print()
75
+
76
+ success = quick_logits_check(pytorch_file, llamacpp_file)
77
+
78
+ # Exit with appropriate code
79
+ if success:
80
+ print("✅ OK: Lightweight model check successful!")
81
+ print(" Ok to proceed with NMSE check...")
82
+ sys.exit(0)
83
+ else:
84
+ exit_with_warning(f"❌ NOK: Top 10 predictions don't match - generation will differ", model_path)
85
+
86
+ if __name__ == "__main__":
87
+ main()
backend/llama.cpp/examples/model-conversion/scripts/causal/convert-model.sh ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ MMPROJ=""
7
+ DEBUG=""
8
+ while [[ $# -gt 0 ]]; do
9
+ case $1 in
10
+ --mmproj)
11
+ MMPROJ="--mmproj"
12
+ shift
13
+ ;;
14
+ --debug)
15
+ DEBUG="1"
16
+ shift
17
+ ;;
18
+ *)
19
+ shift
20
+ ;;
21
+ esac
22
+ done
23
+
24
+ MODEL_NAME="${MODEL_NAME:-$(basename "$MODEL_PATH")}"
25
+ OUTPUT_DIR="${OUTPUT_DIR:-../../models}"
26
+ TYPE="${OUTTYPE:-f16}"
27
+ METADATA_OVERRIDE="${METADATA_OVERRIDE:-}"
28
+ if [[ -n "$MMPROJ" ]]; then
29
+ CONVERTED_MODEL="${OUTPUT_DIR}/mmproj-${MODEL_NAME}.gguf"
30
+ else
31
+ CONVERTED_MODEL="${OUTPUT_DIR}/${MODEL_NAME}.gguf"
32
+ fi
33
+
34
+ echo "Model path: ${MODEL_PATH}"
35
+ echo "Model name: ${MODEL_NAME}"
36
+ echo "Data type: ${TYPE}"
37
+ echo "Converted model path:: ${CONVERTED_MODEL}"
38
+ echo "Metadata override: ${METADATA_OVERRIDE}"
39
+
40
+ if [[ -n "$DEBUG" ]]; then
41
+ CMD_ARGS=("python" "-m" "pdb")
42
+ else
43
+ CMD_ARGS=("python")
44
+ fi
45
+
46
+ CMD_ARGS+=("../../convert_hf_to_gguf.py" "--verbose")
47
+ CMD_ARGS+=("${MODEL_PATH}")
48
+ CMD_ARGS+=("--outfile" "${CONVERTED_MODEL}")
49
+ CMD_ARGS+=("--outtype" "${TYPE}")
50
+ [[ -n "$METADATA_OVERRIDE" ]] && CMD_ARGS+=("--metadata" "${METADATA_OVERRIDE}")
51
+ [[ -n "$MMPROJ" ]] && CMD_ARGS+=("${MMPROJ}")
52
+
53
+ "${CMD_ARGS[@]}"
54
+
55
+ echo ""
56
+ echo "The environment variable CONVERTED_MODEL can be set to this path using:"
57
+ echo "export CONVERTED_MODEL=$(realpath ${CONVERTED_MODEL})"
backend/llama.cpp/examples/model-conversion/scripts/causal/modelcard.template ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model:
3
+ - {base_model}
4
+ ---
5
+ # {model_name} GGUF
6
+
7
+ Recommended way to run this model:
8
+
9
+ ```sh
10
+ llama-server -hf {namespace}/{model_name}-GGUF
11
+ ```
12
+
13
+ Then, access http://localhost:8080
backend/llama.cpp/examples/model-conversion/scripts/causal/run-casual-gen-embeddings-org.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import importlib
6
+ import torch
7
+ import numpy as np
8
+
9
+ from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM
10
+ from pathlib import Path
11
+
12
+ unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
13
+
14
+ parser = argparse.ArgumentParser(description='Process model with specified path')
15
+ parser.add_argument('--model-path', '-m', help='Path to the model')
16
+ args = parser.parse_args()
17
+
18
+ model_path = os.environ.get('MODEL_PATH', args.model_path)
19
+ if model_path is None:
20
+ parser.error("Model path must be specified either via --model-path argument or MODEL_PATH environment variable")
21
+
22
+ config = AutoConfig.from_pretrained(model_path)
23
+
24
+ print("Model type: ", config.model_type)
25
+ print("Vocab size: ", config.vocab_size)
26
+ print("Hidden size: ", config.hidden_size)
27
+ print("Number of layers: ", config.num_hidden_layers)
28
+ print("BOS token id: ", config.bos_token_id)
29
+ print("EOS token id: ", config.eos_token_id)
30
+
31
+ print("Loading model and tokenizer using AutoTokenizer:", model_path)
32
+ tokenizer = AutoTokenizer.from_pretrained(model_path)
33
+
34
+ if unreleased_model_name:
35
+ model_name_lower = unreleased_model_name.lower()
36
+ unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
37
+ class_name = f"{unreleased_model_name}ForCausalLM"
38
+ print(f"Importing unreleased model module: {unreleased_module_path}")
39
+
40
+ try:
41
+ model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
42
+ model = model_class.from_pretrained(model_path)
43
+ except (ImportError, AttributeError) as e:
44
+ print(f"Failed to import or load model: {e}")
45
+ print("Falling back to AutoModelForCausalLM")
46
+ model = AutoModelForCausalLM.from_pretrained(model_path)
47
+ else:
48
+ model = AutoModelForCausalLM.from_pretrained(model_path)
49
+ print(f"Model class: {type(model)}")
50
+ #print(f"Model file: {type(model).__module__}")
51
+
52
+ model_name = os.path.basename(model_path)
53
+ print(f"Model name: {model_name}")
54
+
55
+ prompt = "Hello world today"
56
+ input_ids = tokenizer(prompt, return_tensors="pt").input_ids # ty: ignore[call-non-callable]
57
+ print(f"Input tokens: {input_ids}")
58
+ print(f"Input text: {repr(prompt)}")
59
+ print(f"Tokenized: {tokenizer.convert_ids_to_tokens(input_ids[0])}") # ty: ignore[unresolved-attribute]
60
+
61
+ with torch.no_grad():
62
+ outputs = model(input_ids, output_hidden_states=True)
63
+
64
+ # Extract hidden states from the last layer
65
+ # outputs.hidden_states is a tuple of (num_layers + 1) tensors
66
+ # Index -1 gets the last layer, shape: [batch_size, seq_len, hidden_size]
67
+ last_hidden_states = outputs.hidden_states[-1]
68
+
69
+ # Get embeddings for all tokens
70
+ token_embeddings = last_hidden_states[0].float().cpu().numpy() # Remove batch dimension
71
+
72
+ print(f"Hidden states shape: {last_hidden_states.shape}")
73
+ print(f"Token embeddings shape: {token_embeddings.shape}")
74
+ print(f"Hidden dimension: {token_embeddings.shape[-1]}")
75
+ print(f"Number of tokens: {token_embeddings.shape[0]}")
76
+
77
+ # Save raw token embeddings
78
+ data_dir = Path("data")
79
+ data_dir.mkdir(exist_ok=True)
80
+ bin_filename = data_dir / f"pytorch-{model_name}-embeddings.bin"
81
+ txt_filename = data_dir / f"pytorch-{model_name}-embeddings.txt"
82
+
83
+ # Save all token embeddings as binary
84
+ print(token_embeddings)
85
+ token_embeddings.astype(np.float32).tofile(bin_filename)
86
+
87
+ # Save as text for inspection
88
+ with open(txt_filename, "w") as f:
89
+ for i, embedding in enumerate(token_embeddings):
90
+ for j, val in enumerate(embedding):
91
+ f.write(f"{i} {j} {val:.6f}\n")
92
+
93
+ # Print embeddings per token in the requested format
94
+ print("\nToken embeddings:")
95
+ tokens = tokenizer.convert_ids_to_tokens(input_ids[0]) # ty: ignore[unresolved-attribute]
96
+ for i, embedding in enumerate(token_embeddings):
97
+ # Format: show first few values, ..., then last few values
98
+ if len(embedding) > 10:
99
+ # Show first 3 and last 3 values with ... in between
100
+ first_vals = " ".join(f"{val:8.6f}" for val in embedding[:3])
101
+ last_vals = " ".join(f"{val:8.6f}" for val in embedding[-3:])
102
+ print(f"embedding {i}: {first_vals} ... {last_vals}")
103
+ else:
104
+ # If embedding is short, show all values
105
+ vals = " ".join(f"{val:8.6f}" for val in embedding)
106
+ print(f"embedding {i}: {vals}")
107
+
108
+ # Also show token info for reference
109
+ print(f"\nToken reference:")
110
+ for i, token in enumerate(tokens):
111
+ print(f" Token {i}: {repr(token)}")
112
+
113
+ print(f"Saved bin logits to: {bin_filename}")
114
+ print(f"Saved txt logist to: {txt_filename}")
backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model-embeddings-logits.sh ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # First try command line argument, then environment variable, then file
6
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
7
+ BUILD_DIR="${2:-"$BUILD_DIR"}"
8
+
9
+ # Final check if we have a model path
10
+ if [ -z "$CONVERTED_MODEL" ]; then
11
+ echo "Error: Model path must be provided either as:" >&2
12
+ echo " 1. Command line argument" >&2
13
+ echo " 2. CONVERTED_MODEL environment variable" >&2
14
+ exit 1
15
+ fi
16
+
17
+ if [ -z "$BUILD_DIR" ]; then
18
+ BUILD_DIR="../../build"
19
+ fi
20
+
21
+ cmake --build ${BUILD_DIR} --target llama-debug -j8
22
+
23
+ ${BUILD_DIR}/bin/llama-debug -m $CONVERTED_MODEL --embedding -p "Hello world today" --save-logits
backend/llama.cpp/examples/model-conversion/scripts/causal/run-converted-model.sh ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # First try command line argument, then environment variable, then file
6
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
7
+ MODEL_TESTING_PROMPT="${2:-"$MODEL_TESTING_PROMPT"}"
8
+ BUILD_DIR="${3:-"$BUILD_DIR"}"
9
+
10
+ if [ -z "$MODEL_TESTING_PROMPT" ]; then
11
+ MODEL_TESTING_PROMPT="Hello, my name is"
12
+ fi
13
+
14
+ if [ -z "$BUILD_DIR" ]; then
15
+ BUILD_DIR="../../build"
16
+ fi
17
+
18
+ # Final check if we have a model path
19
+ if [ -z "$CONVERTED_MODEL" ]; then
20
+ echo "Error: Model path must be provided either as:" >&2
21
+ echo " 1. Command line argument" >&2
22
+ echo " 2. CONVERTED_MODEL environment variable" >&2
23
+ exit 1
24
+ fi
25
+
26
+ echo $CONVERTED_MODEL
27
+ echo $MODEL_TESTING_PROMPT
28
+
29
+ cmake --build ${BUILD_DIR} --target llama-debug -j8
30
+
31
+ ${BUILD_DIR}/bin/llama-debug -m "$CONVERTED_MODEL" -p "$MODEL_TESTING_PROMPT" --save-logits
backend/llama.cpp/examples/model-conversion/scripts/causal/run-org-model.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ import importlib
7
+ import torch
8
+ import numpy as np
9
+
10
+ from transformers import AutoTokenizer, AutoModelForCausalLM, AutoModelForImageTextToText, AutoConfig
11
+
12
+ # Add parent directory to path for imports
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
14
+ from utils.common import debug_hook, save_output_data
15
+
16
+ def parse_arguments():
17
+ parser = argparse.ArgumentParser(description="Process model with specified path")
18
+ parser.add_argument("--model-path", "-m", help="Path to the model")
19
+ parser.add_argument("--prompt-file", "-f", help="Optional prompt file", required=False)
20
+ parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose debug output")
21
+ parser.add_argument("--device", "-d", help="Device to use (cpu, cuda, mps, auto)", default="auto")
22
+ return parser.parse_args()
23
+
24
+ def load_model_and_tokenizer(model_path, device="auto"):
25
+ print("Loading model and tokenizer using AutoTokenizer:", model_path)
26
+ tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
27
+ config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
28
+ multimodal = False
29
+ full_config = config
30
+
31
+ # Determine device_map based on device argument
32
+ if device == "cpu":
33
+ device_map = {"": "cpu"}
34
+ print("Forcing CPU usage")
35
+ elif device == "auto":
36
+ device_map = "auto"
37
+ else:
38
+ device_map = {"": device}
39
+
40
+ print("Model type: ", config.model_type)
41
+ if "vocab_size" not in config and "text_config" in config:
42
+ config = config.text_config
43
+ multimodal = True
44
+
45
+ def print_if_exists(label, obj, attr, default="N/A"):
46
+ val = getattr(obj, attr) if hasattr(obj, attr) else default
47
+ print(f"{label}", val)
48
+
49
+ print_if_exists("Vocab size: ", config, "vocab_size")
50
+ print_if_exists("Hidden size: ", config, "hidden_size")
51
+ print_if_exists("Number of layers: ", config, "num_hidden_layers")
52
+ print_if_exists("BOS token id: ", config, "bos_token_id")
53
+ print_if_exists("EOS token id: ", config, "eos_token_id")
54
+
55
+ unreleased_model_name = os.getenv("UNRELEASED_MODEL_NAME")
56
+ if unreleased_model_name:
57
+ model_name_lower = unreleased_model_name.lower()
58
+ unreleased_module_path = (
59
+ f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
60
+ )
61
+ class_name = f"{unreleased_model_name}ForCausalLM"
62
+ print(f"Importing unreleased model module: {unreleased_module_path}")
63
+
64
+ try:
65
+ model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
66
+ model = model_class.from_pretrained(
67
+ model_path,
68
+ device_map=device_map,
69
+ offload_folder="offload",
70
+ trust_remote_code=True,
71
+ config=config
72
+ )
73
+ except (ImportError, AttributeError) as e:
74
+ print(f"Failed to import or load model: {e}")
75
+ exit(1)
76
+ else:
77
+ if multimodal:
78
+ model = AutoModelForImageTextToText.from_pretrained(
79
+ model_path,
80
+ device_map=device_map,
81
+ offload_folder="offload",
82
+ trust_remote_code=True,
83
+ config=full_config
84
+ )
85
+ else:
86
+ model = AutoModelForCausalLM.from_pretrained(
87
+ model_path,
88
+ device_map=device_map,
89
+ offload_folder="offload",
90
+ trust_remote_code=True,
91
+ config=config
92
+ )
93
+
94
+ print(f"Model class: {model.__class__.__name__}")
95
+
96
+ return model, tokenizer, config
97
+
98
+ def enable_torch_debugging(model):
99
+ for name, module in model.named_modules():
100
+ if len(list(module.children())) == 0: # only leaf modules
101
+ module.register_forward_hook(debug_hook(name))
102
+
103
+ def get_prompt(args):
104
+ if args.prompt_file:
105
+ with open(args.prompt_file, encoding='utf-8') as f:
106
+ return f.read()
107
+ elif os.getenv("MODEL_TESTING_PROMPT"):
108
+ return os.getenv("MODEL_TESTING_PROMPT")
109
+ else:
110
+ return "Hello, my name is"
111
+
112
+ def main():
113
+ args = parse_arguments()
114
+ model_path = os.environ.get("MODEL_PATH", args.model_path)
115
+ if model_path is None:
116
+ print("Error: Model path must be specified either via --model-path argument or MODEL_PATH environment variable")
117
+ sys.exit(1)
118
+
119
+
120
+ model, tokenizer, config = load_model_and_tokenizer(model_path, args.device)
121
+
122
+ if args.verbose:
123
+ enable_torch_debugging(model)
124
+
125
+ model_name = os.path.basename(model_path)
126
+
127
+ # Iterate over the model parameters (the tensors) and get the first one
128
+ # and use it to get the device the model is on.
129
+ device = next(model.parameters()).device
130
+ prompt = get_prompt(args)
131
+ input_ids = tokenizer(prompt, return_tensors="pt").input_ids.to(device)
132
+ token_ids = input_ids[0].cpu().tolist()
133
+
134
+ print(f"Input tokens: {input_ids}")
135
+ print(f"Input text: {repr(prompt)}")
136
+ print(f"Tokenized: {tokenizer.convert_ids_to_tokens(input_ids[0])}")
137
+
138
+ batch_size = 512
139
+
140
+ with torch.no_grad():
141
+ past = None
142
+ outputs = None
143
+ for i in range(0, input_ids.size(1), batch_size):
144
+ print(f"Processing chunk with tokens {i} to {i + batch_size}")
145
+ chunk = input_ids[:, i:i + batch_size]
146
+ outputs = model(chunk.to(model.device), past_key_values=past, use_cache=True)
147
+ past = outputs.past_key_values
148
+
149
+ logits = outputs.logits # type: ignore
150
+
151
+ # Extract logits for the last token (next token prediction)
152
+ last_logits = logits[0, -1, :].float().cpu().numpy()
153
+
154
+ print(f"Logits shape: {logits.shape}")
155
+ print(f"Last token logits shape: {last_logits.shape}")
156
+ print(f"Vocab size: {len(last_logits)}")
157
+
158
+ # Print some sample logits for quick verification
159
+ print(f"First 10 logits: {last_logits[:10]}")
160
+ print(f"Last 10 logits: {last_logits[-10:]}")
161
+
162
+ # Show top 5 predicted tokens
163
+ top_indices = np.argsort(last_logits)[-5:][::-1]
164
+ print("Top 5 predictions:")
165
+ for idx in top_indices:
166
+ token = tokenizer.decode([idx])
167
+ print(f" Token {idx} ({repr(token)}): {last_logits[idx]:.6f}")
168
+
169
+ save_output_data(last_logits, token_ids, prompt, model_name)
170
+
171
+ if __name__ == "__main__":
172
+ main()
backend/llama.cpp/examples/model-conversion/scripts/embedding/compare-embeddings-logits.sh ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ MODEL_PATH=""
7
+ MODEL_NAME=""
8
+ PROMPTS_FILE=""
9
+
10
+ # First argument is always model path
11
+ if [ $# -gt 0 ] && [[ "$1" != --* ]]; then
12
+ MODEL_PATH="$1"
13
+ shift
14
+ fi
15
+
16
+ # Parse remaining arguments
17
+ while [[ $# -gt 0 ]]; do
18
+ case $1 in
19
+ --prompts-file|-pf)
20
+ PROMPTS_FILE="$2"
21
+ shift 2
22
+ ;;
23
+ *)
24
+ # If MODEL_NAME not set and this isn't a flag, use as model name
25
+ if [ -z "$MODEL_NAME" ] && [[ "$1" != --* ]]; then
26
+ MODEL_NAME="$1"
27
+ fi
28
+ shift
29
+ ;;
30
+ esac
31
+ done
32
+
33
+ # Set defaults
34
+ MODEL_PATH="${MODEL_PATH:-"$EMBEDDING_MODEL_PATH"}"
35
+ MODEL_NAME="${MODEL_NAME:-$(basename "$MODEL_PATH")}"
36
+
37
+ CONVERTED_MODEL_PATH="${CONVERTED_EMBEDDING_PATH:-"$CONVERTED_EMBEDDING_MODEL"}"
38
+ CONVERTED_MODEL_NAME="${CONVERTED_MODEL_NAME:-$(basename "$CONVERTED_MODEL_PATH" .gguf)}"
39
+
40
+ if [ -t 0 ]; then
41
+ CPP_EMBEDDINGS="data/llamacpp-${CONVERTED_MODEL_NAME}-embeddings.bin"
42
+ else
43
+ # Process piped JSON data and convert to binary (matching logits.cpp format)
44
+ TEMP_FILE=$(mktemp /tmp/tmp.XXXXXX.binn)
45
+ python3 -c "
46
+ import json
47
+ import sys
48
+ import struct
49
+
50
+ data = json.load(sys.stdin)
51
+
52
+ # Flatten all embeddings completely
53
+ flattened = []
54
+ for item in data:
55
+ embedding = item['embedding']
56
+ for token_embedding in embedding:
57
+ flattened.extend(token_embedding)
58
+
59
+ print(f'Total embedding values: {len(flattened)}', file=sys.stderr)
60
+
61
+ # Write as binary floats - matches logitc.cpp fwrite format
62
+ with open('$TEMP_FILE', 'wb') as f:
63
+ for value in flattened:
64
+ f.write(struct.pack('f', value))
65
+ "
66
+ CPP_EMBEDDINGS="$TEMP_FILE"
67
+ trap "rm -f $TEMP_FILE" EXIT
68
+ fi
69
+
70
+ # Build the semantic_check.py command
71
+ SEMANTIC_CMD="python scripts/utils/semantic_check.py --model-path $MODEL_PATH \
72
+ --python-embeddings data/pytorch-${MODEL_NAME}-embeddings.bin \
73
+ --cpp-embeddings $CPP_EMBEDDINGS"
74
+
75
+ # Add prompts file if specified, otherwise use default prompt
76
+ if [ -n "$PROMPTS_FILE" ]; then
77
+ SEMANTIC_CMD="$SEMANTIC_CMD --prompts-file \"$PROMPTS_FILE\""
78
+ else
79
+ SEMANTIC_CMD="$SEMANTIC_CMD --prompt \"Hello world today\""
80
+ fi
81
+
82
+ # Execute the command
83
+ eval $SEMANTIC_CMD
84
+
backend/llama.cpp/examples/model-conversion/scripts/embedding/convert-model.sh ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ SENTENCE_TRANSFORMERS=""
7
+ while [[ $# -gt 0 ]]; do
8
+ case $1 in
9
+ -st|--sentence-transformers)
10
+ SENTENCE_TRANSFORMERS="--sentence-transformers-dense-modules"
11
+ shift
12
+ ;;
13
+ *)
14
+ echo "Unknown option: $1"
15
+ exit 1
16
+ ;;
17
+ esac
18
+ done
19
+
20
+ MODEL_NAME="${MODEL_NAME:-$(basename "$EMBEDDING_MODEL_PATH")}"
21
+ OUTPUT_DIR="${OUTPUT_DIR:-../../models}"
22
+ TYPE="${OUTTYPE:-f16}"
23
+ METADATA_OVERRIDE="${METADATA_OVERRIDE:-}"
24
+ CONVERTED_MODEL="${OUTPUT_DIR}/${MODEL_NAME}.gguf"
25
+
26
+ echo "Model path: ${EMBEDDING_MODEL_PATH}"
27
+ echo "Model name: ${MODEL_NAME}"
28
+ echo "Data type: ${TYPE}"
29
+ echo "Converted model path:: ${CONVERTED_MODEL}"
30
+ python ../../convert_hf_to_gguf.py --verbose \
31
+ ${EMBEDDING_MODEL_PATH} \
32
+ --outfile ${CONVERTED_MODEL} \
33
+ --outtype ${TYPE} \
34
+ ${SENTENCE_TRANSFORMERS}
35
+
36
+ echo ""
37
+ echo "The environment variable CONVERTED_EMBEDDING MODEL can be set to this path using:"
38
+ echo "export CONVERTED_EMBEDDING_MODEL=$(realpath ${CONVERTED_MODEL})"
backend/llama.cpp/examples/model-conversion/scripts/embedding/modelcard.template ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ base_model:
3
+ - {base_model}
4
+ ---
5
+ # {model_name} GGUF
6
+
7
+ Recommended way to run this model:
8
+
9
+ ```sh
10
+ llama-server -hf {namespace}/{model_name}-GGUF --embeddings
11
+ ```
12
+
13
+ Then the endpoint can be accessed at http://localhost:8080/embedding, for
14
+ example using `curl`:
15
+ ```console
16
+ curl --request POST \
17
+ --url http://localhost:8080/embedding \
18
+ --header "Content-Type: application/json" \
19
+ --data '{{"input": "Hello embeddings"}}' \
20
+ --silent
21
+ ```
22
+
23
+ Alternatively, the `llama-embedding` command line tool can be used:
24
+ ```sh
25
+ llama-embedding -hf {namespace}/{model_name}-GGUF --verbose-prompt -p "Hello embeddings"
26
+ ```
27
+
28
+ #### embd_normalize
29
+ When a model uses pooling, or the pooling method is specified using `--pooling`,
30
+ the normalization can be controlled by the `embd_normalize` parameter.
31
+
32
+ The default value is `2` which means that the embeddings are normalized using
33
+ the Euclidean norm (L2). Other options are:
34
+ * -1 No normalization
35
+ * 0 Max absolute
36
+ * 1 Taxicab
37
+ * 2 Euclidean/L2
38
+ * \>2 P-Norm
39
+
40
+ This can be passed in the request body to `llama-server`, for example:
41
+ ```sh
42
+ --data '{{"input": "Hello embeddings", "embd_normalize": -1}}' \
43
+ ```
44
+
45
+ And for `llama-embedding`, by passing `--embd-normalize <value>`, for example:
46
+ ```sh
47
+ llama-embedding -hf {namespace}/{model_name}-GGUF --embd-normalize -1 -p "Hello embeddings"
48
+ ```
backend/llama.cpp/examples/model-conversion/scripts/embedding/run-converted-model.sh ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ # Parse command line arguments
6
+ CONVERTED_MODEL=""
7
+ PROMPTS_FILE=""
8
+ EMBD_NORMALIZE="2"
9
+
10
+ while [[ $# -gt 0 ]]; do
11
+ case $1 in
12
+ -p|--prompts-file)
13
+ PROMPTS_FILE="$2"
14
+ shift 2
15
+ ;;
16
+ --embd-normalize)
17
+ EMBD_NORMALIZE="$2"
18
+ shift 2
19
+ ;;
20
+ *)
21
+ if [ -z "$CONVERTED_MODEL" ]; then
22
+ CONVERTED_MODEL="$1"
23
+ fi
24
+ shift
25
+ ;;
26
+ esac
27
+ done
28
+
29
+ # First try command line argument, then environment variable
30
+ CONVERTED_MODEL="${CONVERTED_MODEL:-"$CONVERTED_EMBEDDING_MODEL"}"
31
+ BUILD_DIR="${BUILD_DIR:-"../../build"}"
32
+
33
+ # Final check if we have a model path
34
+ if [ -z "$CONVERTED_MODEL" ]; then
35
+ echo "Error: Model path must be provided either as:" >&2
36
+ echo " 1. Command line argument" >&2
37
+ echo " 2. CONVERTED_EMBEDDING_MODEL environment variable" >&2
38
+ exit 1
39
+ fi
40
+
41
+ # Read prompt from file or use default
42
+ if [ -n "$PROMPTS_FILE" ]; then
43
+ if [ ! -f "$PROMPTS_FILE" ]; then
44
+ echo "Error: Prompts file '$PROMPTS_FILE' not found" >&2
45
+ exit 1
46
+ fi
47
+ PROMPT=$(cat "$PROMPTS_FILE")
48
+ else
49
+ PROMPT="Hello world today"
50
+ fi
51
+
52
+ echo $CONVERTED_MODEL
53
+
54
+ cmake --build ${BUILD_DIR} --target llama-debug -j8
55
+ ${BUILD_DIR}/bin/llama-debug -m "$CONVERTED_MODEL" --embedding -p "$PROMPT" --save-logits --embd-normalize $EMBD_NORMALIZE
backend/llama.cpp/examples/model-conversion/scripts/embedding/run-original-model.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import os
5
+ import sys
6
+ import importlib
7
+
8
+ from transformers import AutoTokenizer, AutoConfig, AutoModel
9
+ import torch
10
+
11
+ # Add parent directory to path for imports
12
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
13
+ from utils.common import save_output_data
14
+
15
+
16
+ def parse_arguments():
17
+ parser = argparse.ArgumentParser(description='Run original embedding model')
18
+ parser.add_argument(
19
+ '--model-path',
20
+ '-m',
21
+ help='Path to the model'
22
+ )
23
+ parser.add_argument(
24
+ '--prompts-file',
25
+ '-p',
26
+ help='Path to file containing prompts (one per line)'
27
+ )
28
+ parser.add_argument(
29
+ '--use-sentence-transformers',
30
+ action='store_true',
31
+ help=('Use SentenceTransformer to apply all numbered layers '
32
+ '(01_Pooling, 02_Dense, 03_Dense, 04_Normalize)')
33
+ )
34
+ parser.add_argument(
35
+ '--device',
36
+ '-d',
37
+ help='Device to use (cpu, cuda, mps, auto)',
38
+ default='auto'
39
+ )
40
+ return parser.parse_args()
41
+
42
+
43
+ def load_model_and_tokenizer(model_path, use_sentence_transformers=False, device="auto"):
44
+ if device == "cpu":
45
+ device_map = {"": "cpu"}
46
+ print("Forcing CPU usage")
47
+ elif device == "auto":
48
+ # On Mac, "auto" device_map can cause issues with accelerate
49
+ # So we detect the best device manually
50
+ if torch.cuda.is_available():
51
+ device_map = {"": "cuda"}
52
+ print("Using CUDA")
53
+ elif torch.backends.mps.is_available():
54
+ device_map = {"": "mps"}
55
+ print("Using MPS (Apple Metal)")
56
+ else:
57
+ device_map = {"": "cpu"}
58
+ print("Using CPU")
59
+ else:
60
+ device_map = {"": device}
61
+
62
+ if use_sentence_transformers:
63
+ from sentence_transformers import SentenceTransformer
64
+ print("Using SentenceTransformer to apply all numbered layers")
65
+ model = SentenceTransformer(model_path)
66
+ tokenizer = model.tokenizer
67
+ config = model[0].auto_model.config # ty: ignore[unresolved-attribute]
68
+ else:
69
+ tokenizer = AutoTokenizer.from_pretrained(model_path)
70
+ config = AutoConfig.from_pretrained(model_path, trust_remote_code=True)
71
+
72
+ # This can be used to override the sliding window size for manual testing. This
73
+ # can be useful to verify the sliding window attention mask in the original model
74
+ # and compare it with the converted .gguf model.
75
+ if hasattr(config, 'sliding_window'):
76
+ original_sliding_window = config.sliding_window
77
+ print(f"Modified sliding window: {original_sliding_window} -> {config.sliding_window}")
78
+
79
+ unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
80
+ print(f"Using unreleased model: {unreleased_model_name}")
81
+ if unreleased_model_name:
82
+ model_name_lower = unreleased_model_name.lower()
83
+ unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
84
+ class_name = f"{unreleased_model_name}Model"
85
+ print(f"Importing unreleased model module: {unreleased_module_path}")
86
+
87
+ try:
88
+ model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
89
+ model = model_class.from_pretrained(
90
+ model_path,
91
+ device_map=device_map,
92
+ offload_folder="offload",
93
+ trust_remote_code=True,
94
+ config=config
95
+ )
96
+ except (ImportError, AttributeError) as e:
97
+ print(f"Failed to import or load model: {e}")
98
+ sys.exit(1)
99
+ else:
100
+ model = AutoModel.from_pretrained(
101
+ model_path,
102
+ device_map=device_map,
103
+ offload_folder="offload",
104
+ trust_remote_code=True,
105
+ config=config
106
+ )
107
+ print(f"Model class: {type(model)}")
108
+ print(f"Model file: {type(model).__module__}")
109
+
110
+ # Verify the model is using the correct sliding window
111
+ if hasattr(model.config, 'sliding_window'):
112
+ print(f"Model's sliding_window: {model.config.sliding_window}")
113
+ else:
114
+ print("Model config does not have sliding_window attribute")
115
+
116
+ return model, tokenizer, config
117
+
118
+
119
+ def get_prompt(args):
120
+ if args.prompts_file:
121
+ try:
122
+ with open(args.prompts_file, 'r', encoding='utf-8') as f:
123
+ return f.read().strip()
124
+ except FileNotFoundError:
125
+ print(f"Error: Prompts file '{args.prompts_file}' not found")
126
+ sys.exit(1)
127
+ except Exception as e:
128
+ print(f"Error reading prompts file: {e}")
129
+ sys.exit(1)
130
+ else:
131
+ return "Hello world today"
132
+
133
+
134
+ def main():
135
+ args = parse_arguments()
136
+
137
+ model_path = os.environ.get('EMBEDDING_MODEL_PATH', args.model_path)
138
+ if model_path is None:
139
+ print("Error: Model path must be specified either via --model-path argument "
140
+ "or EMBEDDING_MODEL_PATH environment variable")
141
+ sys.exit(1)
142
+
143
+ # Determine if we should use SentenceTransformer
144
+ use_st = (
145
+ args.use_sentence_transformers or os.environ.get('USE_SENTENCE_TRANSFORMERS', '').lower() in ('1', 'true', 'yes')
146
+ )
147
+
148
+ model, tokenizer, config = load_model_and_tokenizer(model_path, use_st, args.device)
149
+
150
+ # Get the device the model is on
151
+ if not use_st:
152
+ device = next(model.parameters()).device
153
+ else:
154
+ # For SentenceTransformer, get device from the underlying model
155
+ device = next(model[0].auto_model.parameters()).device
156
+
157
+ model_name = os.path.basename(model_path)
158
+
159
+ prompt_text = get_prompt(args)
160
+ texts = [prompt_text]
161
+
162
+ with torch.no_grad():
163
+ if use_st:
164
+ embeddings = model.encode(texts, convert_to_numpy=True)
165
+ all_embeddings = embeddings # Shape: [batch_size, hidden_size]
166
+
167
+ encoded = tokenizer(
168
+ texts,
169
+ padding=True,
170
+ truncation=True,
171
+ return_tensors="pt"
172
+ )
173
+ tokens = encoded['input_ids'][0]
174
+ token_ids = tokens.cpu().tolist()
175
+ token_strings = tokenizer.convert_ids_to_tokens(tokens)
176
+ for i, (token_id, token_str) in enumerate(zip(tokens, token_strings)):
177
+ print(f"{token_id:6d} -> '{token_str}'")
178
+
179
+ print(f"Embeddings shape (after all SentenceTransformer layers): {all_embeddings.shape}")
180
+ print(f"Embedding dimension: {all_embeddings.shape[1] if len(all_embeddings.shape) > 1 else all_embeddings.shape[0]}")
181
+ else:
182
+ # Standard approach: use base model output only
183
+ encoded = tokenizer(
184
+ texts,
185
+ padding=True,
186
+ truncation=True,
187
+ return_tensors="pt"
188
+ )
189
+
190
+ tokens = encoded['input_ids'][0]
191
+ token_ids = tokens.cpu().tolist()
192
+ token_strings = tokenizer.convert_ids_to_tokens(tokens)
193
+ for i, (token_id, token_str) in enumerate(zip(tokens, token_strings)):
194
+ print(f"{token_id:6d} -> '{token_str}'")
195
+
196
+ # Move inputs to the same device as the model
197
+ encoded = {k: v.to(device) for k, v in encoded.items()}
198
+ outputs = model(**encoded)
199
+ hidden_states = outputs.last_hidden_state # Shape: [batch_size, seq_len, hidden_size]
200
+
201
+ all_embeddings = hidden_states[0].float().cpu().numpy() # Shape: [seq_len, hidden_size]
202
+
203
+ print(f"Hidden states shape: {hidden_states.shape}")
204
+ print(f"All embeddings shape: {all_embeddings.shape}")
205
+ print(f"Embedding dimension: {all_embeddings.shape[1]}")
206
+
207
+ if len(all_embeddings.shape) == 1:
208
+ n_embd = all_embeddings.shape[0]
209
+ n_embd_count = 1
210
+ all_embeddings = all_embeddings.reshape(1, -1)
211
+ else:
212
+ n_embd = all_embeddings.shape[1]
213
+ n_embd_count = all_embeddings.shape[0]
214
+
215
+ print()
216
+
217
+ for j in range(n_embd_count):
218
+ embedding = all_embeddings[j]
219
+ print(f"embedding {j}: ", end="")
220
+
221
+ # Print first 3 values
222
+ for i in range(min(3, n_embd)):
223
+ print(f"{embedding[i]:9.6f} ", end="")
224
+
225
+ print(" ... ", end="")
226
+
227
+ # Print last 3 values
228
+ for i in range(n_embd - 3, n_embd):
229
+ print(f"{embedding[i]:9.6f} ", end="")
230
+
231
+ print() # New line
232
+
233
+ print()
234
+
235
+ flattened_embeddings = all_embeddings.flatten()
236
+ print(f"Total values: {len(flattened_embeddings)} ({n_embd_count} embeddings × {n_embd} dimensions)")
237
+ print("")
238
+
239
+ save_output_data(flattened_embeddings, token_ids, prompt_text, model_name, type_suffix="-embeddings")
240
+
241
+
242
+ if __name__ == "__main__":
243
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/__init__.py ADDED
File without changes
backend/llama.cpp/examples/model-conversion/scripts/utils/check-nmse.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import numpy as np
4
+ import sys
5
+ import os
6
+ import argparse
7
+ from pathlib import Path
8
+ from common import get_model_name_from_env_path # type: ignore[import-not-found, ty:unresolved-import]
9
+
10
+ def calculate_nmse(reference, test):
11
+ mse = np.mean((test - reference) ** 2)
12
+ ref_var = np.var(reference)
13
+ if ref_var == 0:
14
+ nmse = float('inf') if mse > 0 else 0.0
15
+ return mse, mse, ref_var
16
+
17
+ nmse = mse / ref_var
18
+
19
+ return nmse, mse, ref_var
20
+
21
+ def load_logits(file_path):
22
+ if not os.path.exists(file_path):
23
+ raise FileNotFoundError(f"File not found: {file_path}")
24
+
25
+ if file_path.suffix == '.npy':
26
+ return np.load(file_path)
27
+ elif file_path.suffix == '.bin':
28
+ return np.fromfile(file_path, dtype=np.float32)
29
+ else:
30
+ # Try to load as text file
31
+ try:
32
+ # If it has index format "0: value", extract just values
33
+ data = []
34
+ with open(file_path, 'r') as f:
35
+ for line in f:
36
+ if ':' in line:
37
+ # Format: "index: value"
38
+ value = float(line.split(':')[1].strip())
39
+ else:
40
+ # Just the value
41
+ value = float(line.strip())
42
+ data.append(value)
43
+ return np.array(data, dtype=np.float32)
44
+ except:
45
+ return np.loadtxt(file_path, dtype=np.float32)
46
+
47
+ def interpret_nmse(nmse):
48
+ """Provide interpretation of NMSE value"""
49
+ if nmse == 0:
50
+ return "Perfect match", "🎉"
51
+ elif nmse < 1e-6:
52
+ return "Essentially identical", "✅"
53
+ elif nmse < 1e-4:
54
+ return "Excellent match", "✅"
55
+ elif nmse < 1e-3:
56
+ return "Very good match", "👍"
57
+ elif nmse < 1e-2:
58
+ return "Good match", "👍"
59
+ elif nmse < 0.1:
60
+ return "Acceptable match", "⚠️"
61
+ elif nmse < 1.0:
62
+ return "Poor match", "❌"
63
+ else:
64
+ return "Very poor match (worse than noise)", "❌"
65
+
66
+ def main():
67
+ parser = argparse.ArgumentParser(description='Validate model logits')
68
+ parser.add_argument('-m', '--model-path', required=True, help='Path to the model directory')
69
+ args = parser.parse_args()
70
+
71
+ model_name = get_model_name_from_env_path('MODEL_PATH')
72
+ data_dir = Path("data")
73
+
74
+ pytorch_file = data_dir / f"pytorch-{model_name}.bin"
75
+
76
+ llamacpp_model_name = get_model_name_from_env_path('CONVERTED_MODEL')
77
+ llamacpp_file = data_dir / f"llamacpp-{llamacpp_model_name}.bin"
78
+
79
+ print(f"Model name: {model_name}")
80
+ print(f"PyTorch logits file: {pytorch_file}")
81
+ print(f"llama.cpp logits file: {llamacpp_file}")
82
+
83
+ reference_file = pytorch_file
84
+ test_file = llamacpp_file
85
+
86
+ print("📊 NMSE Check for Model Comparison")
87
+ print("=" * 50)
88
+ print(f"Reference (ground truth): {reference_file}")
89
+ print(f"Test (to evaluate): {test_file}")
90
+ print()
91
+
92
+ try:
93
+ print("Loading reference logits...")
94
+ reference = load_logits(reference_file)
95
+ print(f" Shape: {reference.shape}, Type: {reference.dtype}")
96
+
97
+ print("Loading test logits...")
98
+ test = load_logits(test_file)
99
+ print(f" Shape: {test.shape}, Type: {test.dtype}")
100
+
101
+ # Check shapes match
102
+ if reference.shape != test.shape:
103
+ print(f"\n❌ Error: Shape mismatch!")
104
+ print(f" Reference: {reference.shape}")
105
+ print(f" Test: {test.shape}")
106
+ sys.exit(1)
107
+
108
+ print(f"\n✅ Shapes match: {reference.shape}")
109
+
110
+ nmse, mse, ref_var = calculate_nmse(reference, test)
111
+
112
+ # Additional metrics
113
+ max_abs_error = np.max(np.abs(test - reference))
114
+ mean_abs_error = np.mean(np.abs(test - reference))
115
+
116
+ # Results
117
+ print(f"\n📈 METRICS")
118
+ print("=" * 30)
119
+ print(f"MSE (Mean Squared Error): {mse:.6e}")
120
+ print(f"Reference Variance: {ref_var:.6e}")
121
+ print(f"NMSE: {nmse:.6e}")
122
+ print(f"Max Absolute Error: {max_abs_error:.6f}")
123
+ print(f"Mean Absolute Error: {mean_abs_error:.6f}")
124
+
125
+ # NMSE in dB (common in signal processing)
126
+ if nmse > 0:
127
+ nmse_db = 10 * np.log10(nmse)
128
+ print(f"NMSE (dB): {nmse_db:.2f} dB")
129
+
130
+ # Interpretation
131
+ interpretation, emoji = interpret_nmse(nmse)
132
+ print(f"\n🎯 INTERPRETATION")
133
+ print("=" * 30)
134
+ print(f"{emoji} {interpretation}")
135
+
136
+ # Detailed guidance
137
+ print(f"\n📋 GUIDANCE")
138
+ print("=" * 30)
139
+ if nmse < 1e-3:
140
+ print("✅ EXCELLENT: Your GGML conversion is working very well!")
141
+ print(" The differences are negligible for practical use.")
142
+ elif nmse < 1e-2:
143
+ print("👍 GOOD: Your GGML conversion is working well.")
144
+ print(" Small differences are likely due to precision/quantization.")
145
+ elif nmse < 0.1:
146
+ print("⚠️ ACCEPTABLE: Conversion is working but with some differences.")
147
+ print(" Check if you're using quantization (Q4, Q8, etc.)")
148
+ print(" Test generation quality to see if it's acceptable.")
149
+ else:
150
+ print("❌ PROBLEMATIC: Large differences detected.")
151
+ print(" Check your conversion process for potential issues.")
152
+ print(" Verify you're using the same model weights.")
153
+
154
+ # NMSE benchmarks
155
+ print(f"\n📚 NMSE BENCHMARKS")
156
+ print("=" * 30)
157
+ print("< 1e-6: Essentially identical")
158
+ print("< 1e-4: Excellent (typical for good conversions)")
159
+ print("< 1e-3: Very good")
160
+ print("< 1e-2: Good (acceptable for most use cases)")
161
+ print("< 0.1: Acceptable (may need verification)")
162
+ print("> 1.0: Poor (worse than random)")
163
+
164
+ # Exit code based on NMSE
165
+ if nmse < 1e-2:
166
+ print(f"\n✅ RESULT: PASS (NMSE = {nmse:.2e})")
167
+ sys.exit(0)
168
+ else:
169
+ print(f"\n❌ RESULT: NEEDS REVIEW (NMSE = {nmse:.2e})")
170
+ sys.exit(1)
171
+
172
+ except Exception as e:
173
+ print(f"❌ Error: {e}")
174
+ sys.exit(1)
175
+
176
+ if __name__ == "__main__":
177
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/common.py ADDED
@@ -0,0 +1,299 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import sys
5
+ import torch
6
+ import transformers
7
+ import json
8
+ import textwrap
9
+ import numpy as np
10
+ from pathlib import Path
11
+
12
+
13
+ def get_model_name_from_env_path(env_path_name):
14
+ model_path = os.getenv(env_path_name)
15
+ if not model_path:
16
+ print(f"Error: {env_path_name} environment variable not set")
17
+ sys.exit(1)
18
+
19
+ if not os.path.exists(model_path):
20
+ print(f"Error: Model file not found: {model_path}")
21
+ sys.exit(1)
22
+
23
+ name = os.path.basename(os.path.normpath(model_path))
24
+ if name.endswith(".gguf"):
25
+ name = name[:-5]
26
+
27
+ return name
28
+
29
+
30
+ def summarize(tensor: torch.Tensor, name: str, max_seq: int = 3, max_vals: int = 3):
31
+ """
32
+ Print a tensor in llama.cpp debug style.
33
+
34
+ Supports:
35
+ - 2D tensors (seq, hidden)
36
+ - 3D tensors (batch, seq, hidden)
37
+ - 4D tensors (batch, seq, heads, dim_per_head) via flattening heads × dim_per_head
38
+
39
+ Shows first and last max_vals of each vector per sequence position.
40
+ """
41
+ t = tensor.detach().to(torch.float32).cpu()
42
+
43
+ # Determine dimensions
44
+ if t.ndim == 3:
45
+ _, s, _ = t.shape
46
+ elif t.ndim == 2:
47
+ _, s = 1, t.shape[0]
48
+ t = t.unsqueeze(0)
49
+ elif t.ndim == 4:
50
+ _, s, _, _ = t.shape
51
+ else:
52
+ print(f"Skipping tensor due to unsupported dimensions: {t.ndim}")
53
+ return
54
+
55
+ ten_shape = t.shape
56
+
57
+ print(f"ggml_debug: {name} = (f32) ... = {{{ten_shape}}}")
58
+ print(" [")
59
+ print(" [")
60
+
61
+ # Determine indices for first and last sequences
62
+ first_indices = list(range(min(s, max_seq)))
63
+ last_indices = list(range(max(0, s - max_seq), s))
64
+
65
+ # Check if there's an overlap between first and last indices or if we're at the edge case of s = 2 * max_seq
66
+ has_overlap = bool(set(first_indices) & set(last_indices)) or (max_seq * 2 == s)
67
+
68
+ # Combine indices
69
+ if has_overlap:
70
+ # If there's overlap, just use the combined unique indices
71
+ indices = sorted(list(set(first_indices + last_indices)))
72
+ separator_index = None
73
+ else:
74
+ # If no overlap, we'll add a separator between first and last sequences
75
+ indices = first_indices + last_indices
76
+ separator_index = len(first_indices)
77
+
78
+ for i, si in enumerate(indices):
79
+ # Add separator if needed
80
+ if separator_index is not None and i == separator_index:
81
+ print(" ...")
82
+
83
+ # Extract appropriate slice
84
+ vec = t[0, si]
85
+ if vec.ndim == 2: # 4D case: flatten heads × dim_per_head
86
+ flat = vec.flatten().tolist()
87
+ else: # 2D or 3D case
88
+ flat = vec.tolist()
89
+
90
+ # First and last slices
91
+ first = flat[:max_vals]
92
+ last = flat[-max_vals:] if len(flat) >= max_vals else flat
93
+ first_str = ", ".join(f"{v:12.4f}" for v in first)
94
+ last_str = ", ".join(f"{v:12.4f}" for v in last)
95
+
96
+ print(f" [{first_str}, ..., {last_str}]")
97
+
98
+ print(" ],")
99
+ print(" ]")
100
+ print(f" sum = {t.sum().item():.6f}\n")
101
+
102
+
103
+ def debug_hook(name):
104
+ def fn(_m, input, output):
105
+ if isinstance(input, torch.Tensor):
106
+ summarize(input, name + "_in")
107
+ elif isinstance(input, (tuple, list)) and len(input) > 0 and isinstance(input[0], torch.Tensor):
108
+ summarize(input[0], name + "_in")
109
+ if isinstance(output, torch.Tensor):
110
+ summarize(output, name + "_out")
111
+ elif isinstance(output, (tuple, list)) and len(output) > 0 and isinstance(output[0], torch.Tensor):
112
+ summarize(output[0], name + "_out")
113
+
114
+ return fn
115
+
116
+
117
+ def setup_rope_debug(model_module_path: str, function_name: str = "apply_rotary_pos_emb"):
118
+ """
119
+ Apply monkey patch to dump RoPE activations for debugging.
120
+
121
+ Args:
122
+ model_module_path: Path to the model module (e.g., "transformers.models.apertus.modeling_apertus")
123
+ function_name: Name of the RoPE function to patch (default: "apply_rotary_pos_emb")
124
+
125
+ Example:
126
+ from utils.common import setup_rope_debug
127
+ setup_rope_debug("transformers.models.apertus.modeling_apertus")
128
+ """
129
+ import importlib
130
+
131
+ # Import the module and get the original function
132
+ module = importlib.import_module(model_module_path)
133
+ orig_rope = getattr(module, function_name)
134
+
135
+ # Set torch print options for better debugging
136
+ torch.set_printoptions(threshold=float('inf'))
137
+ torch.set_printoptions(precision=6, sci_mode=False)
138
+
139
+ def debug_rope(q, k, cos, sin, position_ids=None, unsqueeze_dim=1):
140
+ # log inputs
141
+ summarize(q, "RoPE.q_in")
142
+ summarize(k, "RoPE.k_in")
143
+
144
+ # call original
145
+ q_out, k_out = orig_rope(q, k, cos, sin, position_ids, unsqueeze_dim)
146
+
147
+ # log outputs
148
+ summarize(q_out, "RoPE.q_out")
149
+ summarize(k_out, "RoPE.k_out")
150
+
151
+ return q_out, k_out
152
+
153
+ # Patch it
154
+ setattr(module, function_name, debug_rope)
155
+ print(f"RoPE debug patching applied to {model_module_path}.{function_name}")
156
+
157
+
158
+ def save_output_data(data, tokens, prompt, model_name, type_suffix="", output_dir="data"):
159
+ """
160
+ Save output data (logits/embeddings), tokens, and prompt to files.
161
+
162
+ Args:
163
+ data: numpy array of floats (logits or embeddings)
164
+ tokens: list or array of token IDs
165
+ prompt: string containing the input prompt
166
+ model_name: name of the model
167
+ type_suffix: optional suffix like "-embeddings" (default: "")
168
+ output_dir: directory to save files (default: "data")
169
+
170
+ Creates the following files in output_dir:
171
+ - pytorch-{model_name}{type_suffix}.bin
172
+ - pytorch-{model_name}{type_suffix}.txt
173
+ - pytorch-{model_name}{type_suffix}-prompt.txt
174
+ - pytorch-{model_name}{type_suffix}-tokens.bin
175
+ """
176
+ data_dir = Path(output_dir)
177
+ data_dir.mkdir(exist_ok=True)
178
+ base_path = data_dir / f"pytorch-{model_name}{type_suffix}"
179
+
180
+ # Convert and flatten logits/embeddings
181
+ data = data.cpu().numpy() if isinstance(data, torch.Tensor) else np.asarray(data)
182
+ data = data.flatten() if data.ndim > 1 else data
183
+
184
+ # Save logits/embedding files
185
+ data.astype(np.float32).tofile(f"{base_path}.bin")
186
+ print(f"Data saved to {base_path}.bin")
187
+
188
+ with open(f"{base_path}.txt", "w") as f:
189
+ f.writelines(f"{i}: {value:.6f}\n" for i, value in enumerate(data))
190
+ print(f"Data saved to {base_path}.txt")
191
+
192
+ # Convert and flatten tokens
193
+ tokens = tokens.cpu().numpy() if isinstance(tokens, torch.Tensor) else np.asarray(tokens)
194
+ tokens = tokens.flatten() if tokens.ndim > 1 else tokens
195
+
196
+ # Save token binary file
197
+ tokens.astype(np.int32).tofile(f"{base_path}-tokens.bin")
198
+ print(f"Tokens saved to {base_path}-tokens.bin")
199
+
200
+ # Save prompt file
201
+ with open(f"{base_path}-prompt.txt", "w") as f:
202
+ f.write(f"prompt: {prompt}\n")
203
+ f.write(f"n_tokens: {len(tokens)}\n")
204
+ f.write(f"token ids: {', '.join(str(int(tid)) for tid in tokens)}\n")
205
+ print(f"Prompt saved to {base_path}-prompt.txt")
206
+
207
+
208
+ def compare_tokens(original, converted, type_suffix="", output_dir="data"):
209
+ data_dir = Path(output_dir)
210
+
211
+ # Read tokens from both models
212
+ tokens1_file = data_dir / f"{original}{type_suffix}-tokens.bin"
213
+ tokens2_file = data_dir / f"{converted}{type_suffix}-tokens.bin"
214
+
215
+ if not tokens1_file.exists():
216
+ print(f"Error: Token file not found: {tokens1_file}")
217
+ return False
218
+
219
+ if not tokens2_file.exists():
220
+ print(f"Error: Token file not found: {tokens2_file}")
221
+ return False
222
+
223
+ tokens1 = np.fromfile(tokens1_file, dtype=np.int32)
224
+ tokens2 = np.fromfile(tokens2_file, dtype=np.int32)
225
+
226
+ print(f"\nComparing tokens between:")
227
+ print(f" Original : {original} ({len(tokens1)} tokens)")
228
+ print(f" Converted: {converted} ({len(tokens2)} tokens)")
229
+
230
+ if len(tokens1) != len(tokens2):
231
+ print(f"\n❌ Token count mismatch: {len(tokens1)} vs {len(tokens2)}")
232
+ return False
233
+
234
+ if np.array_equal(tokens1, tokens2):
235
+ print(f"\n✅ All {len(tokens1)} tokens match!")
236
+ return True
237
+
238
+ mismatches = np.where(tokens1 != tokens2)[0]
239
+ print(f"\n❌ Found {len(mismatches)} mismatched tokens:")
240
+
241
+ num_to_show = min(len(mismatches), 10)
242
+ for idx in mismatches[:num_to_show]:
243
+ print(f" Position {idx}: {tokens1[idx]} vs {tokens2[idx]}")
244
+
245
+ if len(mismatches) > num_to_show:
246
+ print(f" ... and {len(mismatches) - num_to_show} more mismatches")
247
+
248
+ return False
249
+
250
+
251
+ def show_version_warning(current_version, model_version):
252
+ if not model_version:
253
+ return False
254
+
255
+ try:
256
+ from packaging.version import parse, InvalidVersion
257
+ try:
258
+ return parse(current_version) < parse(model_version)
259
+ except InvalidVersion:
260
+ return current_version != model_version
261
+ except ImportError:
262
+ return current_version != model_version
263
+
264
+ def get_model_transformers_version(model_path):
265
+ if not model_path:
266
+ return None
267
+
268
+ config_path = Path(model_path) / "config.json"
269
+ if not config_path.is_file():
270
+ return None
271
+
272
+ try:
273
+ with open(config_path, "r", encoding="utf-8") as f:
274
+ config = json.load(f)
275
+ return config.get("transformers_version")
276
+ except (IOError, json.JSONDecodeError) as e:
277
+ print(f"Warning: Could not read or parse {config_path}: {e}", file=sys.stderr)
278
+ return None
279
+
280
+ def exit_with_warning(message, model_path):
281
+ print(message)
282
+
283
+ if model_path and transformers is not None:
284
+ model_transformers_version = get_model_transformers_version(model_path)
285
+ transformers_version = transformers.__version__
286
+ if show_version_warning(transformers_version, model_transformers_version):
287
+ warning_message = f"""
288
+ =====================================================================
289
+ Verification failure might be due to a transformers version mismatch:
290
+
291
+ Current transformers version: {transformers_version}
292
+ Model's required version : {model_transformers_version}
293
+
294
+ Consider installing the version specified by the model's config:
295
+ pip install transformers=={model_transformers_version}
296
+ =====================================================================
297
+ """
298
+ print(textwrap.dedent(warning_message))
299
+ sys.exit(1)
backend/llama.cpp/examples/model-conversion/scripts/utils/compare_tokens.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import sys
5
+ from common import compare_tokens # type: ignore[import-not-found, ty:unresolved-import]
6
+
7
+
8
+ def parse_arguments():
9
+ parser = argparse.ArgumentParser(
10
+ description='Compare tokens between two models',
11
+ formatter_class=argparse.RawDescriptionHelpFormatter,
12
+ epilog="""
13
+ Examples:
14
+ %(prog)s pytorch-gemma-3-270m-it llamacpp-gemma-3-270m-it-bf16
15
+ """
16
+ )
17
+ parser.add_argument(
18
+ 'original',
19
+ help='Original model name'
20
+ )
21
+ parser.add_argument(
22
+ 'converted',
23
+ help='Converted model name'
24
+ )
25
+ parser.add_argument(
26
+ '-s', '--suffix',
27
+ default='',
28
+ help='Type suffix (e.g., "-embeddings")'
29
+ )
30
+ parser.add_argument(
31
+ '-d', '--data-dir',
32
+ default='data',
33
+ help='Directory containing token files (default: data)'
34
+ )
35
+ parser.add_argument(
36
+ '-v', '--verbose',
37
+ action='store_true',
38
+ help='Print prompts from both models'
39
+ )
40
+ return parser.parse_args()
41
+
42
+
43
+ def main():
44
+ args = parse_arguments()
45
+
46
+ if args.verbose:
47
+ from pathlib import Path
48
+ data_dir = Path(args.data_dir)
49
+
50
+ prompt1_file = data_dir / f"{args.original}{args.suffix}-prompt.txt"
51
+ prompt2_file = data_dir / f"{args.converted}{args.suffix}-prompt.txt"
52
+
53
+ if prompt1_file.exists():
54
+ print(f"\nOriginal model prompt ({args.original}):")
55
+ print(f" {prompt1_file.read_text().strip()}")
56
+
57
+ if prompt2_file.exists():
58
+ print(f"\nConverted model prompt ({args.converted}):")
59
+ print(f" {prompt2_file.read_text().strip()}")
60
+
61
+ print()
62
+
63
+ result = compare_tokens(
64
+ args.original,
65
+ args.converted,
66
+ type_suffix=args.suffix,
67
+ output_dir=args.data_dir
68
+ )
69
+
70
+ # Enable the script to be used in shell scripts so that they can check
71
+ # the exit code for success/failure.
72
+ sys.exit(0 if result else 1)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/create-collection-add-model.sh ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+
2
+ #!/usr/bin/env bash
3
+
4
+ COLLECTION_SLUG=$(python ./create_collection.py --return-slug)
5
+ echo "Created collection: $COLLECTION_SLUG"
6
+
7
+ # Use it in the next command
8
+ python add_model_to_collection.py "$COLLECTION_SLUG" "username/my-model"
backend/llama.cpp/examples/model-conversion/scripts/utils/curl-embedding-server.sh ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+ curl --request POST \
3
+ --url http://localhost:8080/embedding \
4
+ --header "Content-Type: application/json" \
5
+ --data '{"input": "Hello world today"}' \
6
+ --silent
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-add-model-to-collection.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from huggingface_hub import HfApi
4
+ import argparse
5
+ import sys
6
+
7
+ def add_model_to_collection(collection_slug, model_id, note=""):
8
+ """
9
+ Add a model to an existing collection
10
+
11
+ Args:
12
+ collection_slug: The slug of the collection (e.g., "username/collection-name-12345")
13
+ model_id: The model repository ID (e.g., "username/model-name")
14
+ note: Optional note about the model
15
+
16
+ Returns:
17
+ True if successful, False if failed
18
+ """
19
+
20
+ # Initialize API
21
+ api = HfApi()
22
+
23
+ try:
24
+ user_info = api.whoami()
25
+ print(f"✅ Authenticated as: {user_info['name']}")
26
+
27
+ # Verify the model exists
28
+ print(f"🔍 Checking if model exists: {model_id}")
29
+ try:
30
+ model_info = api.model_info(model_id)
31
+ except Exception as e:
32
+ print(f"❌ Model not found or not accessible: {model_id}")
33
+ print(f"Error: {e}")
34
+ return False
35
+
36
+ print(f"📚 Adding model to collection...")
37
+ api.add_collection_item(
38
+ collection_slug=collection_slug,
39
+ item_id=model_id,
40
+ item_type="model",
41
+ note=note
42
+ )
43
+
44
+ print(f"✅ Model added to collection successfully!")
45
+ print(f"🔗 Collection URL: https://huggingface.co/collections/{collection_slug}")
46
+
47
+ return True
48
+
49
+ except Exception as e:
50
+ print(f"❌ Error adding model to collection: {e}")
51
+ return False
52
+
53
+ def main():
54
+ # This script requires that the environment variable HF_TOKEN is set with your
55
+ # Hugging Face API token.
56
+ api = HfApi()
57
+
58
+ parser = argparse.ArgumentParser(description='Add model to a Huggingface Collection')
59
+ parser.add_argument('--collection', '-c', help='The collection slug username/collection-hash', required=True)
60
+ parser.add_argument('--model', '-m', help='The model to add to the Collection', required=True)
61
+ parser.add_argument('--note', '-n', help='An optional note/description', required=False)
62
+ args = parser.parse_args()
63
+
64
+ collection = args.collection
65
+ model = args.model
66
+ note = args.note
67
+
68
+ success = add_model_to_collection(
69
+ collection_slug=collection,
70
+ model_id=model,
71
+ note=note
72
+ )
73
+
74
+ if success:
75
+ print("\n🎉 Model added successfully!")
76
+ else:
77
+ print("\n❌ Failed to add model to collection")
78
+ sys.exit(1)
79
+ if __name__ == "__main__":
80
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-collection.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from huggingface_hub import HfApi
4
+ import argparse
5
+ import os
6
+ import sys
7
+
8
+
9
+ def create_collection(title, description, private=False, namespace=None, return_slug=False):
10
+ """
11
+ Create a new collection on Hugging Face
12
+
13
+ Args:
14
+ title: Collection title
15
+ description: Collection description
16
+ private: Whether the collection should be private (default: False)
17
+ namespace: Optional namespace (defaults to your username)
18
+
19
+ Returns:
20
+ Collection object if successful, None if failed
21
+ """
22
+
23
+ # Check if HF_TOKEN is available
24
+ token = os.getenv("HF_TOKEN") or os.getenv("HUGGINGFACE_HUB_TOKEN")
25
+ if not token:
26
+ print("❌ No HF_TOKEN or HUGGINGFACE_HUB_TOKEN found in environment variables")
27
+ print("Please set your Hugging Face token as an environment variable")
28
+ return None
29
+
30
+ # Initialize API
31
+ api = HfApi()
32
+
33
+ try:
34
+ # Test authentication first
35
+ user_info = api.whoami()
36
+ if not return_slug:
37
+ print(f"✅ Authenticated as: {user_info['name']}")
38
+
39
+ # Create the collection
40
+ if not return_slug:
41
+ print(f"📚 Creating collection: '{title}'...")
42
+ collection = api.create_collection(
43
+ title=title,
44
+ description=description,
45
+ private=private,
46
+ namespace=namespace
47
+ )
48
+
49
+ if not return_slug:
50
+ print(f"✅ Collection created successfully!")
51
+ print(f"📋 Collection slug: {collection.slug}")
52
+ print(f"🔗 Collection URL: https://huggingface.co/collections/{collection.slug}")
53
+
54
+ return collection
55
+
56
+ except Exception as e:
57
+ print(f"❌ Error creating collection: {e}")
58
+ return None
59
+
60
+ def main():
61
+ # This script requires that the environment variable HF_TOKEN is set with your
62
+ # Hugging Face API token.
63
+ api = HfApi()
64
+
65
+ parser = argparse.ArgumentParser(description='Create a Huggingface Collection')
66
+ parser.add_argument('--name', '-n', help='The name/title of the Collection', required=True)
67
+ parser.add_argument('--description', '-d', help='The description for the Collection', required=True)
68
+ parser.add_argument('--namespace', '-ns', help='The namespace to add the Collection to', required=True)
69
+ parser.add_argument('--private', '-p', help='Create a private Collection', action='store_true') # Fixed
70
+ parser.add_argument('--return-slug', '-s', help='Only output the collection slug', action='store_true') # Fixed
71
+
72
+ args = parser.parse_args()
73
+
74
+ name = args.name
75
+ description = args.description
76
+ private = args.private
77
+ namespace = args.namespace
78
+ return_slug = args.return_slug
79
+
80
+ if not return_slug:
81
+ print("🚀 Creating Hugging Face Collection")
82
+ print(f"Title: {name}")
83
+ print(f"Description: {description}")
84
+ print(f"Namespace: {namespace}")
85
+ print(f"Private: {private}")
86
+
87
+ collection = create_collection(
88
+ title=name,
89
+ description=description,
90
+ private=private,
91
+ namespace=namespace,
92
+ return_slug=return_slug
93
+ )
94
+
95
+ if collection:
96
+ if return_slug:
97
+ print(collection.slug)
98
+ else:
99
+ print("\n🎉 Collection created successfully!")
100
+ print(f"Use this slug to add models: {collection.slug}")
101
+ else:
102
+ print("\n❌ Failed to create collection")
103
+ sys.exit(1)
104
+
105
+ if __name__ == "__main__":
106
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-create-model.py ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from huggingface_hub import HfApi
4
+ import argparse
5
+
6
+ # This script requires that the environment variable HF_TOKEN is set with your
7
+ # Hugging Face API token.
8
+ api = HfApi()
9
+
10
+ def load_template_and_substitute(template_path, **kwargs):
11
+ try:
12
+ with open(template_path, 'r', encoding='utf-8') as f:
13
+ template_content = f.read()
14
+
15
+ return template_content.format(**kwargs)
16
+ except FileNotFoundError:
17
+ print(f"Template file '{template_path}' not found!")
18
+ return None
19
+ except KeyError as e:
20
+ print(f"Missing template variable: {e}")
21
+ return None
22
+
23
+ parser = argparse.ArgumentParser(description='Create a new Hugging Face model repository')
24
+ parser.add_argument('--model-name', '-m', help='Name for the model', required=True)
25
+ parser.add_argument('--namespace', '-ns', help='Namespace to add the model to', required=True)
26
+ parser.add_argument('--org-base-model', '-b', help='Original Base model name', default="")
27
+ parser.add_argument('--no-card', action='store_true', help='Skip creating model card')
28
+ parser.add_argument('--private', '-p', action='store_true', help='Create private model')
29
+ parser.add_argument('--embedding', '-e', action='store_true', help='Use embedding model card template')
30
+ parser.add_argument('--dry-run', '-d', action='store_true', help='Print repository info and template without creating repository')
31
+
32
+ args = parser.parse_args()
33
+
34
+ repo_id = f"{args.namespace}/{args.model_name}-GGUF"
35
+ print("Repository ID: ", repo_id)
36
+
37
+ repo_url = None
38
+ if not args.dry_run:
39
+ repo_url = api.create_repo(
40
+ repo_id=repo_id,
41
+ repo_type="model",
42
+ private=args.private,
43
+ exist_ok=False
44
+ )
45
+
46
+ if not args.no_card:
47
+ if args.embedding:
48
+ template_path = "scripts/embedding/modelcard.template"
49
+ else:
50
+ template_path = "scripts/causal/modelcard.template"
51
+
52
+ print("Template path: ", template_path)
53
+
54
+ model_card_content = load_template_and_substitute(
55
+ template_path,
56
+ model_name=args.model_name,
57
+ namespace=args.namespace,
58
+ base_model=args.org_base_model,
59
+ )
60
+
61
+ if args.dry_run:
62
+ print("\nTemplate Content:\n")
63
+ print(model_card_content)
64
+ else:
65
+ if model_card_content:
66
+ api.upload_file(
67
+ path_or_fileobj=model_card_content.encode('utf-8'),
68
+ path_in_repo="README.md",
69
+ repo_id=repo_id
70
+ )
71
+ print("Model card created successfully.")
72
+ else:
73
+ print("Failed to create model card.")
74
+
75
+ if not args.dry_run and repo_url:
76
+ print(f"Repository created: {repo_url}")
77
+
78
+
backend/llama.cpp/examples/model-conversion/scripts/utils/hf-upload-gguf-model.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ from huggingface_hub import HfApi
4
+ import argparse
5
+ import os
6
+
7
+ def upload_gguf_file(local_file_path, repo_id, filename_in_repo=None):
8
+ """
9
+ Upload a GGUF file to a Hugging Face model repository
10
+
11
+ Args:
12
+ local_file_path: Path to your local GGUF file
13
+ repo_id: Your repository ID (e.g., "username/model-name")
14
+ filename_in_repo: Optional custom name for the file in the repo
15
+ """
16
+
17
+ if not os.path.exists(local_file_path):
18
+ print(f"❌ File not found: {local_file_path}")
19
+ return False
20
+
21
+ if filename_in_repo is None:
22
+ filename_in_repo = os.path.basename(local_file_path)
23
+
24
+ if filename_in_repo is None or filename_in_repo == "":
25
+ filename_in_repo = os.path.basename(local_file_path)
26
+
27
+ print(f"📤 Uploading {local_file_path} to {repo_id}/{filename_in_repo}")
28
+
29
+ api = HfApi()
30
+
31
+ try:
32
+ api.upload_file(
33
+ path_or_fileobj=local_file_path,
34
+ path_in_repo=filename_in_repo,
35
+ repo_id=repo_id,
36
+ repo_type="model",
37
+ commit_message=f"Upload {filename_in_repo}"
38
+ )
39
+
40
+ print("✅ Upload successful!")
41
+ print(f"🔗 File available at: https://huggingface.co/{repo_id}/blob/main/{filename_in_repo}")
42
+ return True
43
+
44
+ except Exception as e:
45
+ print(f"❌ Upload failed: {e}")
46
+ return False
47
+
48
+ # This script requires that the environment variable HF_TOKEN is set with your
49
+ # Hugging Face API token.
50
+ api = HfApi()
51
+
52
+ parser = argparse.ArgumentParser(description='Upload a GGUF model to a Huggingface model repository')
53
+ parser.add_argument('--gguf-model-path', '-m', help='The GGUF model file to upload', required=True)
54
+ parser.add_argument('--repo-id', '-r', help='The repository to upload to', required=True)
55
+ parser.add_argument('--name', '-o', help='The name in the model repository', required=False)
56
+ args = parser.parse_args()
57
+
58
+ upload_gguf_file(args.gguf_model_path, args.repo_id, args.name)
backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-converted-model.sh ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ # First try command line argument, then environment variable, then file
4
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
5
+
6
+ # Final check if we have a model path
7
+ if [ -z "$CONVERTED_MODEL" ]; then
8
+ echo "Error: Model path must be provided either as:" >&2
9
+ echo " 1. Command line argument" >&2
10
+ echo " 2. CONVERTED_MODEL environment variable" >&2
11
+ exit 1
12
+ fi
13
+
14
+ ../../gguf-py/gguf/scripts/gguf_dump.py $CONVERTED_MODEL
backend/llama.cpp/examples/model-conversion/scripts/utils/inspect-org-model.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import argparse
4
+ import json
5
+ import os
6
+ import re
7
+ import struct
8
+ import sys
9
+ from pathlib import Path
10
+ from typing import Optional
11
+ from safetensors import safe_open
12
+
13
+
14
+ MODEL_SAFETENSORS_FILE = "model.safetensors"
15
+ MODEL_SAFETENSORS_INDEX = "model.safetensors.index.json"
16
+
17
+ DTYPE_SIZES = {
18
+ "F64": 8, "I64": 8, "U64": 8,
19
+ "F32": 4, "I32": 4, "U32": 4,
20
+ "F16": 2, "BF16": 2, "I16": 2, "U16": 2,
21
+ "I8": 1, "U8": 1, "BOOL": 1,
22
+ "F8_E4M3": 1, "F8_E5M2": 1,
23
+ }
24
+
25
+ SIZE_UNITS = ['B', 'KB', 'MB', 'GB', 'TB']
26
+
27
+
28
+ def get_weight_map(model_path: Path) -> Optional[dict[str, str]]:
29
+ index_file = model_path / MODEL_SAFETENSORS_INDEX
30
+
31
+ if index_file.exists():
32
+ with open(index_file, 'r') as f:
33
+ index = json.load(f)
34
+ return index.get("weight_map", {})
35
+
36
+ return None
37
+
38
+
39
+ def get_all_tensor_names(model_path: Path) -> list[str]:
40
+ weight_map = get_weight_map(model_path)
41
+
42
+ if weight_map is not None:
43
+ return list(weight_map.keys())
44
+
45
+ single_file = model_path / MODEL_SAFETENSORS_FILE
46
+ if single_file.exists():
47
+ try:
48
+ with safe_open(single_file, framework="pt", device="cpu") as f:
49
+ return list(f.keys())
50
+ except Exception as e:
51
+ print(f"Error reading {single_file}: {e}")
52
+ sys.exit(1)
53
+
54
+ print(f"Error: No safetensors files found in {model_path}")
55
+ sys.exit(1)
56
+
57
+
58
+ def find_tensor_file(model_path: Path, tensor_name: str) -> Optional[str]:
59
+ weight_map = get_weight_map(model_path)
60
+
61
+ if weight_map is not None:
62
+ return weight_map.get(tensor_name)
63
+
64
+ single_file = model_path / MODEL_SAFETENSORS_FILE
65
+ if single_file.exists():
66
+ return single_file.name
67
+
68
+ return None
69
+
70
+
71
+ def read_safetensors_header(file_path: Path) -> dict:
72
+ with open(file_path, 'rb') as f:
73
+ header_size = struct.unpack('<Q', f.read(8))[0]
74
+ return json.loads(f.read(header_size))
75
+
76
+
77
+ def get_tensor_size_bytes(tensor_meta: dict) -> int:
78
+ offsets = tensor_meta.get("data_offsets")
79
+ if offsets and len(offsets) == 2:
80
+ return offsets[1] - offsets[0]
81
+ n_elements = 1
82
+ for d in tensor_meta.get("shape", []):
83
+ n_elements *= d
84
+ return n_elements * DTYPE_SIZES.get(tensor_meta.get("dtype", "F32"), 4)
85
+
86
+
87
+ def format_size(size_bytes: int) -> str:
88
+ val = float(size_bytes)
89
+ for unit in SIZE_UNITS[:-1]:
90
+ if val < 1024.0:
91
+ return f"{val:.2f} {unit}"
92
+ val /= 1024.0
93
+ return f"{val:.2f} {SIZE_UNITS[-1]}"
94
+
95
+
96
+ def get_all_tensor_metadata(model_path: Path) -> dict[str, dict]:
97
+ weight_map = get_weight_map(model_path)
98
+
99
+ if weight_map is not None:
100
+ file_to_tensors: dict[str, list[str]] = {}
101
+ for tensor_name, file_name in weight_map.items():
102
+ file_to_tensors.setdefault(file_name, []).append(tensor_name)
103
+
104
+ all_metadata: dict[str, dict] = {}
105
+ for file_name, tensor_names in file_to_tensors.items():
106
+ try:
107
+ header = read_safetensors_header(model_path / file_name)
108
+ for tensor_name in tensor_names:
109
+ if tensor_name in header:
110
+ all_metadata[tensor_name] = header[tensor_name]
111
+ except Exception as e:
112
+ print(f"Warning: Could not read header from {file_name}: {e}", file=sys.stderr)
113
+ return all_metadata
114
+
115
+ single_file = model_path / MODEL_SAFETENSORS_FILE
116
+ if single_file.exists():
117
+ try:
118
+ header = read_safetensors_header(single_file)
119
+ return {k: v for k, v in header.items() if k != "__metadata__"}
120
+ except Exception as e:
121
+ print(f"Error reading {single_file}: {e}")
122
+ sys.exit(1)
123
+
124
+ print(f"Error: No safetensors files found in {model_path}")
125
+ sys.exit(1)
126
+
127
+
128
+ def normalize_tensor_name(tensor_name: str) -> str:
129
+ normalized = re.sub(r'\.\d+\.', '.#.', tensor_name)
130
+ normalized = re.sub(r'\.\d+$', '.#', normalized)
131
+ return normalized
132
+
133
+
134
+ def list_all_tensors(
135
+ model_path: Path,
136
+ short: bool = False,
137
+ show_sizes: bool = False,
138
+ ):
139
+ tensor_names = get_all_tensor_names(model_path)
140
+
141
+ metadata: Optional[dict[str, dict]] = None
142
+ if show_sizes:
143
+ metadata = get_all_tensor_metadata(model_path)
144
+
145
+ total_bytes = 0
146
+
147
+ if short:
148
+ seen: dict[str, str] = {}
149
+ for tensor_name in sorted(tensor_names):
150
+ normalized = normalize_tensor_name(tensor_name)
151
+ if normalized not in seen:
152
+ seen[normalized] = tensor_name
153
+ display_pairs = list(sorted(seen.items()))
154
+ name_width = max((len(n) for n, _ in display_pairs), default=0)
155
+ for normalized, first_name in display_pairs:
156
+ if metadata and first_name in metadata:
157
+ m = metadata[first_name]
158
+ size = get_tensor_size_bytes(m)
159
+ total_bytes += size
160
+ print(f"{normalized:{name_width}} {m.get('dtype', '?'):6s} {str(m.get('shape', '')):30s} {format_size(size)}")
161
+ else:
162
+ print(normalized)
163
+ else:
164
+ name_width = max((len(n) for n in tensor_names), default=0)
165
+ for tensor_name in sorted(tensor_names):
166
+ if metadata and tensor_name in metadata:
167
+ m = metadata[tensor_name]
168
+ size = get_tensor_size_bytes(m)
169
+ total_bytes += size
170
+ print(f"{tensor_name:{name_width}} {m.get('dtype', '?'):6s} {str(m.get('shape', '')):30s} {format_size(size)}")
171
+ else:
172
+ print(tensor_name)
173
+
174
+ if show_sizes:
175
+ print(f"\nTotal: {format_size(total_bytes)}")
176
+
177
+
178
+ def print_tensor_info(model_path: Path, tensor_name: str, num_values: Optional[int] = None):
179
+ tensor_file = find_tensor_file(model_path, tensor_name)
180
+
181
+ if tensor_file is None:
182
+ print(f"Error: Could not find tensor '{tensor_name}' in model index")
183
+ print(f"Model path: {model_path}")
184
+ sys.exit(1)
185
+
186
+ file_path = model_path / tensor_file
187
+
188
+ try:
189
+ header = read_safetensors_header(file_path)
190
+ tensor_meta = header.get(tensor_name, {})
191
+ dtype_str = tensor_meta.get("dtype")
192
+
193
+ with safe_open(file_path, framework="pt", device="cpu") as f:
194
+ if tensor_name in f.keys():
195
+ tensor_slice = f.get_slice(tensor_name)
196
+ shape = tensor_slice.get_shape()
197
+ print(f"Tensor: {tensor_name}")
198
+ print(f"File: {tensor_file}")
199
+ print(f"Shape: {shape}")
200
+ if dtype_str:
201
+ print(f"Dtype: {dtype_str}")
202
+ if tensor_meta:
203
+ print(f"Size: {format_size(get_tensor_size_bytes(tensor_meta))}")
204
+ if num_values is not None:
205
+ tensor = f.get_tensor(tensor_name)
206
+ if not dtype_str:
207
+ print(f"Dtype: {tensor.dtype}")
208
+ flat = tensor.flatten()
209
+ n = min(num_values, flat.numel())
210
+ print(f"Values: {flat[:n].tolist()}")
211
+ else:
212
+ print(f"Error: Tensor '{tensor_name}' not found in {tensor_file}")
213
+ sys.exit(1)
214
+
215
+ except FileNotFoundError:
216
+ print(f"Error: The file '{file_path}' was not found.")
217
+ sys.exit(1)
218
+ except Exception as e:
219
+ print(f"An error occurred: {e}")
220
+ sys.exit(1)
221
+
222
+
223
+ def main():
224
+ parser = argparse.ArgumentParser(
225
+ description="Print tensor information from a safetensors model"
226
+ )
227
+ parser.add_argument(
228
+ "tensor_name",
229
+ nargs="?",
230
+ help="Name of the tensor to inspect"
231
+ )
232
+ parser.add_argument(
233
+ "-m", "--model-path",
234
+ type=Path,
235
+ help="Path to the model directory (default: MODEL_PATH environment variable)"
236
+ )
237
+ parser.add_argument(
238
+ "-l", "--list-all-short",
239
+ action="store_true",
240
+ help="List unique tensor patterns (layer numbers replaced with #)"
241
+ )
242
+ parser.add_argument(
243
+ "-la", "--list-all",
244
+ action="store_true",
245
+ help="List all tensor names with actual layer numbers"
246
+ )
247
+ parser.add_argument(
248
+ "-n", "--num-values",
249
+ nargs="?",
250
+ const=10,
251
+ default=None,
252
+ type=int,
253
+ metavar="N",
254
+ help="Print the first N values of the tensor flattened (default: 10 if flag is given without a number)"
255
+ )
256
+ parser.add_argument(
257
+ "-s", "--sizes",
258
+ action="store_true",
259
+ help="Show dtype, shape, and size for each tensor when listing"
260
+ )
261
+
262
+ args = parser.parse_args()
263
+
264
+ model_path = args.model_path
265
+ if model_path is None:
266
+ model_path_str = os.environ.get("MODEL_PATH")
267
+ if model_path_str is None:
268
+ print("Error: --model-path not provided and MODEL_PATH environment variable not set")
269
+ sys.exit(1)
270
+ model_path = Path(model_path_str)
271
+
272
+ if not model_path.exists():
273
+ print(f"Error: Model path does not exist: {model_path}")
274
+ sys.exit(1)
275
+
276
+ if not model_path.is_dir():
277
+ print(f"Error: Model path is not a directory: {model_path}")
278
+ sys.exit(1)
279
+
280
+ if args.list_all_short or args.list_all:
281
+ list_all_tensors(model_path, short=args.list_all_short, show_sizes=args.sizes)
282
+ else:
283
+ if args.tensor_name is None:
284
+ print("Error: tensor_name is required when not using --list-all-short or --list-all")
285
+ sys.exit(1)
286
+ print_tensor_info(model_path, args.tensor_name, args.num_values)
287
+
288
+
289
+ if __name__ == "__main__":
290
+ main()
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-gen.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
6
+ BUILD_DIR="${2:-"$BUILD_DIR"}"
7
+
8
+ # Final check if we have a model path
9
+ if [ -z "$CONVERTED_MODEL" ]; then
10
+ echo "Error: Model path must be provided either as:" >&2
11
+ echo " 1. Command line argument" >&2
12
+ echo " 2. CONVERTED_MODEL environment variable" >&2
13
+ exit 1
14
+ fi
15
+
16
+ # Check if data/wikitext-2-raw directory exists
17
+ if [ ! -d "ppl/wikitext-2-raw" ]; then
18
+ echo "ppl/wikitext-2-raw directory does not exist. Downloading..." >&2
19
+ mkdir -p ppl
20
+ pushd ppl
21
+ ./../../../scripts/get-wikitext-2.sh
22
+ popd
23
+ fi
24
+
25
+ mkdir -p ppl
26
+ OUTPUTFILE="ppl/$(basename $CONVERTED_MODEL).kld"
27
+ echo "Model: $CONVERTED_MODEL"
28
+
29
+ if [ -z "$BUILD_DIR" ]; then
30
+ BUILD_DIR="../../build"
31
+ fi
32
+
33
+ cmake --build $BUILD_DIR --target llama-perplexity -j8
34
+
35
+ ${BUILD_DIR}/bin/llama-perplexity -m $CONVERTED_MODEL \
36
+ -f ppl/wikitext-2-raw/wiki.test.raw \
37
+ --kl-divergence-base $OUTPUTFILE
38
+
39
+ echo "Generated logits in $OUTPUTFILE"
40
+
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run-simple.sh ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ QUANTIZED_MODEL="${1:-"$QUANTIZED_MODEL"}"
6
+ BUILD_DIR="${2:-"$BUILD_DIR"}"
7
+
8
+ if [ -z "$QUANTIZED_MODEL" ]; then
9
+ echo "Error: Model path must be provided either as:" >&2
10
+ echo " 1. Command line argument" >&2
11
+ echo " 2. QUANTIZED_MODEL environment variable" >&2
12
+ exit 1
13
+ fi
14
+
15
+ # Check if data/wikitext-2-raw directory exists
16
+ if [ ! -d "ppl/wikitext-2-raw" ]; then
17
+ echo "ppl/wikitext-2-raw directory does not exist. Downloading..." >&2
18
+ mkdir -p ppl
19
+ pushd ppl
20
+ ./../../../scripts/get-wikitext-2.sh
21
+ popd
22
+ fi
23
+
24
+ if [ -z "$BUILD_DIR" ]; then
25
+ BUILD_DIR="../../build"
26
+ fi
27
+
28
+ cmake --build $BUILD_DIR --target llama-perplexity -j8
29
+
30
+ ${BUILD_DIR}/bin/llama-perplexity -m $QUANTIZED_MODEL -f ppl/wikitext-2-raw/wiki.test.raw
31
+
32
+
backend/llama.cpp/examples/model-conversion/scripts/utils/perplexity-run.sh ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ QUANTIZED_MODEL="${1:-"$QUANTIZED_MODEL"}"
6
+ LOGITS_FILE="${2:-"$LOGITS_FILE"}"
7
+ BUILD_DIR="${3:-"$BUILD_DIR"}"
8
+
9
+ if [ -z "$QUANTIZED_MODEL" ]; then
10
+ echo "Error: Model path must be provided either as:" >&2
11
+ echo " 1. Command line argument" >&2
12
+ echo " 2. QUANTIZED_MODEL environment variable" >&2
13
+ exit 1
14
+ fi
15
+
16
+ if [ ! -f ${LOGITS_FILE} ]; then
17
+ echo "Error: logits file '${LOGITS_FILE} was not found"
18
+ echo "Did you run the perplexity-gen.sh script?"
19
+ exit 1
20
+ fi
21
+
22
+ if [ -z "$BUILD_DIR" ]; then
23
+ BUILD_DIR="../../build"
24
+ fi
25
+
26
+ echo "Model: $QUANTIZED_MODEL"
27
+ echo "Data file: $LOGITS_FILE"
28
+
29
+ cmake --build $BUILD_DIR --target llama-perplexity -j8
30
+
31
+ ${BUILD_DIR}/bin/llama-perplexity -m $QUANTIZED_MODEL \
32
+ --kl-divergence-base $LOGITS_FILE \
33
+ --kl-divergence
backend/llama.cpp/examples/model-conversion/scripts/utils/quantize.sh ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
6
+ QUANTIZED_TYPE="${2:-"$QUANTIZED_TYPE"}"
7
+ TOKEN_EMBD_TYPE="${3:-"${TOKEN_EMBD_TYPE}"}"
8
+ OUTPUT_TYPE="${4:-"${OUTPUT_TYPE}"}"
9
+ BUILD_DIR="${5:-"$BUILD_DIR"}"
10
+ QUANTIZED_MODEL=$CONVERTED_MODEL
11
+
12
+ # Final check if we have a model path
13
+ if [ -z "$CONVERTED_MODEL" ]; then
14
+ echo "Error: Model path must be provided either as:" >&2
15
+ echo " 1. Command line argument" >&2
16
+ echo " 2. CONVERTED_MODEL environment variable" >&2
17
+ exit 1
18
+ fi
19
+
20
+ if [ -z "$QUANTIZED_TYPE" ]; then
21
+ echo "Error: QUANTIZED_TYPE is required" >&2
22
+ exit 1
23
+ fi
24
+
25
+ echo $CONVERTED_MODEL
26
+
27
+ # Process the quantized model filename
28
+ if [[ "$QUANTIZED_MODEL" == *.gguf ]]; then
29
+ # Remove .gguf suffix, add quantized type, then add .gguf back
30
+ BASE_NAME="${QUANTIZED_MODEL%.gguf}"
31
+ QUANTIZED_MODEL="${BASE_NAME}-${QUANTIZED_TYPE}.gguf"
32
+ else
33
+ echo "Error: QUANTIZED_MODEL must end with .gguf extension" >&2
34
+ exit 1
35
+ fi
36
+
37
+ if [ -z "$BUILD_DIR" ]; then
38
+ BUILD_DIR="../../build"
39
+ fi
40
+
41
+ cmake --build $BUILD_DIR --target llama-quantize -j8
42
+
43
+ echo $TOKEN_EMBD_TYPE
44
+ echo $OUTPUT_TYPE
45
+
46
+ CMD_ARGS=("${BUILD_DIR}/bin/llama-quantize")
47
+ [[ -n "$TOKEN_EMBD_TYPE" ]] && CMD_ARGS+=("--token-embedding-type" "$TOKEN_EMBD_TYPE")
48
+ [[ -n "$OUTPUT_TYPE" ]] && CMD_ARGS+=("--output-tensor-type" "$OUTPUT_TYPE")
49
+ CMD_ARGS+=("$CONVERTED_MODEL" "$QUANTIZED_MODEL" "$QUANTIZED_TYPE")
50
+
51
+ "${CMD_ARGS[@]}"
52
+
53
+ echo "Quantized model saved to: $QUANTIZED_MODEL"
backend/llama.cpp/examples/model-conversion/scripts/utils/run-embedding-server.sh ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+ #
5
+ # First try command line argument, then environment variable, then file
6
+ CONVERTED_MODEL="${1:-"$CONVERTED_MODEL"}"
7
+ BUILD_DIR="${2:-"$BUILD_DIR"}"
8
+
9
+ # Final check if we have a model path
10
+ if [ -z "$CONVERTED_MODEL" ]; then
11
+ echo "Error: Model path must be provided either as:" >&2
12
+ echo " 1. Command line argument" >&2
13
+ echo " 2. CONVERTED_MODEL environment variable" >&2
14
+ exit 1
15
+ fi
16
+
17
+ if [ -z "$BUILD_DIR" ]; then
18
+ BUILD_DIR="../../build"
19
+ fi
20
+
21
+ echo $CONVERTED_MODEL
22
+
23
+ cmake --build $BUILD_DIR --target llama-server
24
+
25
+ ${BUILD_DIR}/bin/llama-server -m $CONVERTED_MODEL \
26
+ --embedding \
27
+ --pooling none
backend/llama.cpp/examples/model-conversion/scripts/utils/semantic_check.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import numpy as np
4
+ import argparse
5
+ import os
6
+ import importlib
7
+ from pathlib import Path
8
+
9
+ from transformers import AutoTokenizer, AutoConfig, AutoModelForCausalLM, AutoModel
10
+ from common import compare_tokens, exit_with_warning # type: ignore[import-not-found, ty:unresolved-import]
11
+
12
+ unreleased_model_name = os.getenv('UNRELEASED_MODEL_NAME')
13
+
14
+ def cosine_similarity(a, b=None):
15
+ a = np.asarray(a)
16
+ if b is None:
17
+ b = a
18
+ else:
19
+ b = np.asarray(b)
20
+
21
+ if a.ndim == 1:
22
+ a = a.reshape(1, -1)
23
+ if b.ndim == 1:
24
+ b = b.reshape(1, -1)
25
+
26
+ a_norms = np.linalg.norm(a, axis=1, keepdims=True)
27
+ b_norms = np.linalg.norm(b, axis=1, keepdims=True)
28
+
29
+ a_norms = np.where(a_norms == 0, 1e-8, a_norms)
30
+ b_norms = np.where(b_norms == 0, 1e-8, b_norms)
31
+
32
+ a_normalized = a / a_norms
33
+ b_normalized = b / b_norms
34
+
35
+ # Compute cosine similarity
36
+ return np.dot(a_normalized, b_normalized.T)
37
+
38
+ def load_embeddings_from_file(filename, n_tokens, n_embd):
39
+ embeddings = np.fromfile(filename, dtype=np.float32)
40
+ # Check if this is pooled (single embedding) or per-token embeddings
41
+ if len(embeddings) == n_embd:
42
+ return embeddings.reshape(1, n_embd)
43
+ else:
44
+ return embeddings.reshape(n_tokens, n_embd)
45
+
46
+ def test_single_prompt_similarity(python_emb, cpp_emb, tokens, prompt):
47
+ np.set_printoptions(suppress=True, precision=6)
48
+ print("pytorch embeddings:");
49
+ print(python_emb)
50
+ print("llama.cpp embeddings:");
51
+ print(cpp_emb)
52
+ print(f"\n=== Prompt: '{prompt}' ===")
53
+ print(f"Tokens: {tokens}")
54
+ print(f"Embeddings shape: Python {python_emb.shape}, llama.cpp {cpp_emb.shape}")
55
+
56
+ n_tokens = len(tokens)
57
+ is_pooled = python_emb.shape[0] == 1
58
+
59
+ if is_pooled:
60
+ print(f"\n[Pooled Embeddings Mode - comparing single sentence embeddings]")
61
+
62
+ # 1. Direct embedding comparison for pooled embeddings
63
+ print(f"\n1. Raw Embedding Magnitude Comparison:")
64
+ py_mag = np.linalg.norm(python_emb[0])
65
+ cpp_mag = np.linalg.norm(cpp_emb[0])
66
+ ratio = py_mag / cpp_mag if cpp_mag > 0 else float('inf')
67
+ print(f" Pooled embedding: Python={py_mag:.3f}, llama.cpp={cpp_mag:.3f}, ratio={ratio:.3f}")
68
+
69
+ # 2. Cross-model similarity for pooled embeddings
70
+ print(f"\n2. Cross-Model Pooled Embedding Similarity:")
71
+ sim = cosine_similarity([python_emb[0]], [cpp_emb[0]])[0][0]
72
+ print(f" Cosine similarity: {sim:.6f}")
73
+
74
+ return {
75
+ 'cross_model_similarities': [sim],
76
+ 'similarity_matrix_diff': np.array([[0.0]]),
77
+ 'max_diff': 0.0,
78
+ 'mean_diff': 0.0,
79
+ 'rms_diff': 0.0
80
+ }
81
+ else:
82
+ # Original per-token comparison logic
83
+ # 1. Direct embedding comparison
84
+ print(f"\n1. Raw Embedding Magnitude Comparison:")
85
+ # Check if the distance of each token embedding from the origin and compare
86
+ # if the vectors are on the same "sphere". This does not tell us about
87
+ # direction (meaning of the token embedding), just magnitude.
88
+ for i in range(n_tokens):
89
+ py_mag = np.linalg.norm(python_emb[i]) # calculate standard euclidean norm for Python embeddings
90
+ cpp_mag = np.linalg.norm(cpp_emb[i]) # calculate standard euclidean norm for llama.cpp embeddings
91
+ ratio = py_mag / cpp_mag if cpp_mag > 0 else float('inf')
92
+ print(f" Token {i} ({tokens[i]}): Python={py_mag:.3f}, llama.cpp={cpp_mag:.3f}, ratio={ratio:.3f}")
93
+
94
+ # 2. Cosine similarity between tokens within each model
95
+ # Here we check the direction of token embeddings to see if the have the
96
+ # same meaning (similarity). This is done by calculating cosine similarity
97
+ # of a pair of token embeddings within each model.
98
+ print(f"\n2. Within-Model Token Similarities:")
99
+ print(" Python model:")
100
+ for i in range(n_tokens):
101
+ for j in range(i+1, n_tokens):
102
+ sim = cosine_similarity([python_emb[i]], [python_emb[j]])[0][0]
103
+ print(f" {tokens[i]} ↔ {tokens[j]}: {sim:.4f}")
104
+
105
+ print(" llama.cpp model:")
106
+ for i in range(n_tokens):
107
+ for j in range(i+1, n_tokens):
108
+ sim = cosine_similarity([cpp_emb[i]], [cpp_emb[j]])[0][0]
109
+ print(f" {tokens[i]} ↔ {tokens[j]}: {sim:.4f}")
110
+
111
+ # 3. Cross-model similarity (same token position)
112
+ print(f"\n3. Cross-Model Same-Token Similarities:")
113
+ for i in range(n_tokens):
114
+ sim = cosine_similarity([python_emb[i]], [cpp_emb[i]])[0][0]
115
+ print(f" Token {i} ({tokens[i]}): {sim:.4f}")
116
+
117
+ # 4. Similarity matrix comparison
118
+ print(f"\n4. Similarity Matrix Differences:")
119
+ py_sim_matrix = cosine_similarity(python_emb)
120
+ cpp_sim_matrix = cosine_similarity(cpp_emb)
121
+ diff_matrix = np.abs(py_sim_matrix - cpp_sim_matrix)
122
+
123
+ print(f" Max difference: {np.max(diff_matrix):.4f}")
124
+ print(f" Mean difference: {np.mean(diff_matrix):.4f}")
125
+ print(f" RMS difference: {np.sqrt(np.mean(diff_matrix**2)):.4f}")
126
+
127
+ return {
128
+ 'cross_model_similarities': [cosine_similarity([python_emb[i]], [cpp_emb[i]])[0][0] for i in range(n_tokens)],
129
+ 'similarity_matrix_diff': diff_matrix,
130
+ 'max_diff': np.max(diff_matrix),
131
+ 'mean_diff': np.mean(diff_matrix),
132
+ 'rms_diff': np.sqrt(np.mean(diff_matrix**2))
133
+ }
134
+
135
+ def read_prompt_from_file(file_path):
136
+ try:
137
+ with open(file_path, 'r', encoding='utf-8') as f:
138
+ return f.read().strip()
139
+ except FileNotFoundError:
140
+ print(f"Error: Prompts file '{file_path}' not found")
141
+ exit(1)
142
+ except Exception as e:
143
+ print(f"Error reading prompts file: {e}")
144
+ exit(1)
145
+
146
+ def main():
147
+ parser = argparse.ArgumentParser(description='Test semantic similarity between Python and llama.cpp embeddings')
148
+ parser.add_argument('--model-path', '-m', required=True, help='Path to the original Python model')
149
+ parser.add_argument('--python-embeddings', '-pe', help='Path to pytorch embeddings "logits" binary file')
150
+ parser.add_argument('--cpp-embeddings', '-ce', help='Path to llama.cpp embeddings "logits" binary file')
151
+ parser.add_argument('--causal', '-c', default=False, help='if the model is causal (default: false)', action='store_true')
152
+ parser.add_argument('--prompt', '-p', default='Hello world today', help='Test prompt')
153
+ parser.add_argument('--prompts-file', '-pf', help='Path to file containing prompts')
154
+
155
+ args = parser.parse_args()
156
+
157
+ if args.prompts_file:
158
+ prompt = read_prompt_from_file(args.prompts_file)
159
+ else:
160
+ prompt = args.prompt
161
+
162
+ python_emb_path = Path(args.python_embeddings)
163
+ cpp_emb_path = Path(args.cpp_embeddings)
164
+
165
+ # Extract base names (e.g., "pytorch-model-name-embeddings.bin" -> "pytorch-model-name")
166
+ python_model_name = python_emb_path.stem.replace("-embeddings", "")
167
+ cpp_model_name = cpp_emb_path.stem.replace("-embeddings", "")
168
+
169
+ print("Semantic Similarity Test Between Python and llama.cpp Embedding Models")
170
+ print("=" * 70)
171
+
172
+ # First verify tokens match before comparing embeddings
173
+ print("\n🔍 Token Comparison Check")
174
+ print("=" * 70)
175
+ data_dir = python_emb_path.parent
176
+ if not compare_tokens(python_model_name, cpp_model_name, type_suffix="-embeddings", output_dir=str(data_dir)):
177
+ exit_with_warning("\n❌ Token mismatch detected", args.model_path)
178
+ print()
179
+
180
+ # Single prompt detailed comparison
181
+ print(f"\nTesting with prompt: '{prompt}'")
182
+
183
+ # Load the python model to get configuration information and also to load the tokenizer.
184
+ print("Loading model and tokenizer using AutoTokenizer:", args.model_path)
185
+ tokenizer = AutoTokenizer.from_pretrained(args.model_path)
186
+ config = AutoConfig.from_pretrained(args.model_path, trust_remote_code=True)
187
+
188
+ if unreleased_model_name:
189
+ model_name_lower = unreleased_model_name.lower()
190
+ unreleased_module_path = f"transformers.models.{model_name_lower}.modular_{model_name_lower}"
191
+ if args.causal:
192
+ class_name = f"{unreleased_model_name}ForCausalLM"
193
+ else:
194
+ class_name = f"{unreleased_model_name}Model"
195
+ print(f"Model class: {class_name}")
196
+ print(f"Importing unreleased model module: {unreleased_module_path}")
197
+
198
+ try:
199
+ model_class = getattr(importlib.import_module(unreleased_module_path), class_name)
200
+ model = model_class.from_pretrained(args.model_path)
201
+ except (ImportError, AttributeError) as e:
202
+ print(f"Failed to import or load model: {e}")
203
+ exit(1)
204
+ else:
205
+ if args.causal:
206
+ model = AutoModelForCausalLM.from_pretrained(args.model_path, trust_remote_code=True)
207
+ else:
208
+ model = AutoModel.from_pretrained(args.model_path, trust_remote_code=True)
209
+
210
+ encoded = tokenizer(prompt, return_tensors="pt") # ty: ignore[call-non-callable]
211
+ tokens = tokenizer.convert_ids_to_tokens(encoded['input_ids'][0]) # ty: ignore[unresolved-attribute]
212
+ n_tokens = len(tokens)
213
+ print(f"n_tokens: {n_tokens}");
214
+ print(f"hidden_size: {model.config.hidden_size}")
215
+
216
+ # Load binary embeddings from data directory.
217
+ llamacpp_embeddings = load_embeddings_from_file(args.cpp_embeddings, n_tokens, model.config.hidden_size)
218
+ python_embeddings = load_embeddings_from_file(args.python_embeddings, n_tokens, model.config.hidden_size)
219
+
220
+ # Run comparison
221
+ results = test_single_prompt_similarity(python_embeddings, llamacpp_embeddings, tokens, prompt)
222
+
223
+ # Summary
224
+ print(f"\n=== SUMMARY ===")
225
+ avg_cross_sim = np.mean(results['cross_model_similarities'])
226
+ print(f"Average cross-model similarity: {avg_cross_sim:.4f}")
227
+ print(f"Similarity matrix RMS difference: {results['rms_diff']:.4f}")
228
+
229
+ # Quality assessment
230
+ if avg_cross_sim > 0.95:
231
+ print("✅ EXCELLENT: Models are highly similar")
232
+ elif avg_cross_sim > 0.90:
233
+ print("✅ VERY GOOD: Models are very similar")
234
+ elif avg_cross_sim > 0.80:
235
+ print("⚠️ GOOD: Models are reasonably similar")
236
+ elif avg_cross_sim > 0.70:
237
+ print("⚠️ FAIR: Models have some differences")
238
+ else:
239
+ exit_with_warning("❌ POOR: Models are significantly different", args.model_path)
240
+
241
+ if __name__ == "__main__":
242
+ main()
backend/llama.cpp/examples/parallel/CMakeLists.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ set(TARGET llama-parallel)
2
+ add_executable(${TARGET} parallel.cpp)
3
+ install(TARGETS ${TARGET} RUNTIME)
4
+ target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
5
+ target_compile_features(${TARGET} PRIVATE cxx_std_17)
backend/llama.cpp/examples/parallel/README.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # llama.cpp/example/parallel
2
+
3
+ Simplified simulation of serving incoming requests in parallel
4
+
5
+ ## Example
6
+
7
+ Generate 128 client requests (`-ns 128`), simulating 8 concurrent clients (`-np 8`). The system prompt is shared (`-pps`), meaning that it is computed once at the start. The client requests consist of up to 10 junk questions (`--junk 10`) followed by the actual question.
8
+
9
+ ```bash
10
+ llama-parallel -m model.gguf -np 8 -ns 128 --top-k 1 -pps --junk 10 -c 16384
11
+ ```
12
+
13
+ > [!NOTE]
14
+ > It's recommended to use base models with this example. Instruction tuned models might not be able to properly follow the custom chat template specified here, so the results might not be as expected.
backend/llama.cpp/examples/parallel/parallel.cpp ADDED
@@ -0,0 +1,520 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // A basic application simulating a server with multiple clients.
2
+ // The clients submit requests to the server and they are processed in parallel.
3
+
4
+ #include "arg.h"
5
+ #include "common.h"
6
+ #include "sampling.h"
7
+ #include "log.h"
8
+ #include "llama.h"
9
+
10
+ #include <algorithm>
11
+ #include <clocale>
12
+ #include <cmath>
13
+ #include <cstdio>
14
+ #include <string>
15
+ #include <vector>
16
+ #include <ctime>
17
+
18
+ // trim whitespace from the beginning and end of a string
19
+ static std::string trim(const std::string & str) {
20
+ size_t start = 0;
21
+ size_t end = str.size();
22
+
23
+ while (start < end && isspace(str[start])) {
24
+ start += 1;
25
+ }
26
+
27
+ while (end > start && isspace(str[end - 1])) {
28
+ end -= 1;
29
+ }
30
+
31
+ return str.substr(start, end - start);
32
+ }
33
+
34
+ static std::string k_system =
35
+ R"(Transcript of a never ending dialog, where the User interacts with an Assistant.
36
+ The Assistant is helpful, kind, honest, good at writing, and never fails to answer the User's requests immediately and with precision.
37
+
38
+ User:
39
+ Recommend a nice restaurant in the area.
40
+ Assistant:
41
+ I recommend the restaurant "The Golden Duck". It is a 5 star restaurant with a great view of the city. The food is delicious and the service is excellent. The prices are reasonable and the portions are generous. The restaurant is located at 123 Main Street, New York, NY 10001. The phone number is (212) 555-1234. The hours are Monday through Friday from 11:00 am to 10:00 pm. The restaurant is closed on Saturdays and Sundays.
42
+ User:
43
+ Who is Richard Feynman?
44
+ Assistant:
45
+ Richard Feynman was an American physicist who is best known for his work in quantum mechanics and particle physics. He was awarded the Nobel Prize in Physics in 1965 for his contributions to the development of quantum electrodynamics. He was a popular lecturer and author, and he wrote several books, including "Surely You're Joking, Mr. Feynman!" and "What Do You Care What Other People Think?".
46
+ )";
47
+
48
+ static std::vector<std::string> k_questions = {
49
+ "What is the tallest mountain in the world?",
50
+ "Who was the first person to win two Nobel Prizes?",
51
+ "Which country invented paper?",
52
+ "What organ is primarily responsible for pumping blood throughout the body?",
53
+ "Which planet is known for its prominent ring system?",
54
+ "Who directed the movie 'Inception'?",
55
+ "What is the freezing point of water in Fahrenheit?",
56
+ "Which animal is known to have the longest lifespan?",
57
+ "What language has the most native speakers worldwide?",
58
+ "What is the capital city of Canada?",
59
+ "Who is credited with inventing the World Wide Web?",
60
+ "Which metal is liquid at room temperature?",
61
+ "What is the term for an animal that eats both plants and meat?",
62
+ "Who painted 'The Starry Night'?",
63
+ "What gas do humans exhale that plants use for photosynthesis?",
64
+ "What year did World War II end?",
65
+ "Which continent has the most countries?",
66
+ "Who wrote the novel 'Frankenstein'?",
67
+ "What does DNA stand for?",
68
+ "What is the main ingredient in traditional Japanese miso soup?"
69
+ };
70
+
71
+ static std::vector<std::string> k_answers = {
72
+ "The tallest mountain in the world is Mount Everest.",
73
+ "Marie Curie was the first person to win two Nobel Prizes.",
74
+ "Paper was invented in China.",
75
+ "The heart is the organ responsible for pumping blood.",
76
+ "Saturn is known for its prominent ring system.",
77
+ "Christopher Nolan directed the movie 'Inception'.",
78
+ "The freezing point of water in Fahrenheit is 32°F.",
79
+ "The bowhead whale is known to have the longest lifespan among mammals.",
80
+ "Mandarin Chinese has the most native speakers in the world.",
81
+ "The capital city of Canada is Ottawa.",
82
+ "Tim Berners-Lee is credited with inventing the World Wide Web.",
83
+ "Mercury is the metal that is liquid at room temperature.",
84
+ "An animal that eats both plants and meat is called an omnivore.",
85
+ "'The Starry Night' was painted by Vincent van Gogh.",
86
+ "Humans exhale carbon dioxide, which plants use in photosynthesis.",
87
+ "World War II ended in 1945.",
88
+ "Africa is the continent with the most countries.",
89
+ "The novel 'Frankenstein' was written by Mary Shelley.",
90
+ "DNA stands for Deoxyribonucleic Acid.",
91
+ "The main ingredient in traditional Japanese miso soup is fermented soybean paste."
92
+ };
93
+
94
+ static std::vector<std::string> k_prompts = {
95
+ "What is the meaning of life?",
96
+ "Tell me an interesting fact about llamas.",
97
+ "What is the best way to cook a steak?",
98
+ "Are you familiar with the Special Theory of Relativity and can you explain it to me?",
99
+ "Recommend some interesting books to read.",
100
+ "What is the best way to learn a new language?",
101
+ "How to get a job at Google?",
102
+ "If you could have any superpower, what would it be?",
103
+ "I want to learn how to play the piano. What would be the best way to do it?",
104
+ };
105
+
106
+ struct client {
107
+ ~client() {
108
+ if (smpl) {
109
+ common_sampler_free(smpl);
110
+ }
111
+ }
112
+
113
+ int32_t id = 0;
114
+
115
+ llama_seq_id seq_id = -1;
116
+
117
+ llama_token sampled;
118
+
119
+ int64_t t_start_prompt;
120
+ int64_t t_start_gen;
121
+
122
+ int32_t n_past = 0;
123
+ int32_t n_prompt = 0;
124
+ int32_t n_decoded = 0;
125
+ int32_t i_batch = -1;
126
+
127
+ std::string input;
128
+ std::string prompt;
129
+ std::string response;
130
+
131
+ struct common_sampler * smpl = nullptr;
132
+ };
133
+
134
+ static void print_date_time() {
135
+ std::time_t current_time = std::time(nullptr);
136
+ std::tm* local_time = std::localtime(&current_time);
137
+ char buffer[80];
138
+ strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", local_time);
139
+
140
+ LOG_INF("\n");
141
+ LOG_INF("\033[35mrun parameters as of %s\033[0m\n", buffer);
142
+ LOG_INF("\n");
143
+ }
144
+
145
+ // Define a split string function to ...
146
+ static std::vector<std::string> split_string(const std::string& input, char delimiter) {
147
+ std::vector<std::string> tokens;
148
+ std::istringstream stream(input);
149
+ std::string token;
150
+ while (std::getline(stream, token, delimiter)) {
151
+ tokens.push_back(token);
152
+ }
153
+ return tokens;
154
+ }
155
+
156
+ int main(int argc, char ** argv) {
157
+ std::setlocale(LC_NUMERIC, "C");
158
+
159
+ srand(1234);
160
+
161
+ common_params params;
162
+
163
+ params.n_predict = 128;
164
+ params.n_junk = 1;
165
+
166
+ common_init();
167
+
168
+ if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_PARALLEL)) {
169
+ return 1;
170
+ }
171
+
172
+ // number of simultaneous "clients" to simulate
173
+ const int32_t n_clients = params.n_parallel;
174
+
175
+ // dedicate one sequence to the system prompt
176
+ params.n_parallel += 1;
177
+
178
+ // requests to simulate
179
+ const int32_t n_seq = params.n_sequences;
180
+
181
+ // insert new requests as soon as the previous one is done
182
+ const bool cont_batching = params.cont_batching;
183
+
184
+ // is the system prompt shared in the cache
185
+ const bool is_sp_shared = params.is_pp_shared;
186
+
187
+ // extra text to insert in each client's prompt in order to make it larger
188
+ const int32_t n_junk = std::max(1, params.n_junk);
189
+
190
+ // signed seed, use negative values to indicate different seeds for the different clients
191
+ const int32_t & sseed = params.sampling.seed;
192
+
193
+ // init llama.cpp
194
+ llama_backend_init();
195
+ llama_numa_init(params.numa);
196
+
197
+ // load the target model
198
+ auto llama_init = common_init_from_params(params);
199
+
200
+ auto * model = llama_init->model();
201
+ auto * ctx = llama_init->context();
202
+
203
+ auto * mem = llama_get_memory(ctx);
204
+
205
+ const llama_vocab * vocab = llama_model_get_vocab(model);
206
+
207
+ // load the prompts from an external file if there are any
208
+ if (params.prompt.empty()) {
209
+ LOG_INF("\033[32mNo new questions so proceed with build-in defaults.\033[0m\n");
210
+ } else {
211
+ // Output each line of the input params.prompts vector and copy to k_prompts
212
+ int index = 0;
213
+ LOG_INF("\033[32mNow printing the external prompt file %s\033[0m\n\n", params.prompt_file.c_str());
214
+
215
+ std::vector<std::string> prompts = split_string(params.prompt, '\n');
216
+ for (const auto& prompt : prompts) {
217
+ k_prompts.resize(index + 1);
218
+ k_prompts[index] = prompt;
219
+ index++;
220
+ LOG_INF("%3d prompt: %s\n", index, prompt.c_str());
221
+ }
222
+ }
223
+
224
+ LOG_INF("\n\n");
225
+
226
+ const int n_ctx = llama_n_ctx(ctx);
227
+
228
+ if (sseed >= 0) {
229
+ LOG_INF("%s: initializing all samplers with the same RNG seed: %d (use a negative seed to have different seeds)\n", __func__, sseed);
230
+ } else {
231
+ LOG_INF("%s: initializing samplers with different RNG seeds, starting from %d\n", __func__, sseed);
232
+ }
233
+
234
+ std::vector<client> clients(n_clients);
235
+ for (size_t i = 0; i < clients.size(); ++i) {
236
+ auto & client = clients[i];
237
+ client.id = i;
238
+ client.smpl = common_sampler_init(model, params.sampling);
239
+
240
+ if (sseed < 0) {
241
+ params.sampling.seed--;
242
+ }
243
+ }
244
+
245
+ std::vector<llama_token> tokens_system;
246
+
247
+ tokens_system = common_tokenize(ctx, k_system, true);
248
+ const int32_t n_tokens_system = tokens_system.size();
249
+
250
+ llama_seq_id g_seq_id = 0;
251
+
252
+ // the max batch size is as large as the context to handle cases where we get very long input prompt from multiple
253
+ // users. regardless of the size, the main loop will chunk the batch into a maximum of params.n_batch tokens at a time
254
+ llama_batch batch = llama_batch_init(n_ctx, 0, 1);
255
+
256
+ int32_t n_total_prompt = 0;
257
+ int32_t n_total_gen = 0;
258
+ int32_t n_cache_miss = 0;
259
+
260
+ const auto t_main_start = ggml_time_us();
261
+
262
+ LOG_INF("%s: Simulating parallel requests from clients:\n", __func__);
263
+ LOG_INF("%s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d\n", __func__, n_clients, n_seq, cont_batching, n_tokens_system);
264
+ LOG_INF("\n");
265
+
266
+ if (is_sp_shared) {
267
+ LOG_INF("%s: Evaluating the system prompt ...\n", __func__);
268
+
269
+ for (int32_t i = 0; i < n_tokens_system; ++i) {
270
+ common_batch_add(batch, tokens_system[i], i, { 0 }, false);
271
+ }
272
+
273
+ if (llama_decode(ctx, batch) != 0) {
274
+ LOG_ERR("%s: llama_decode() failed\n", __func__);
275
+ return 1;
276
+ }
277
+
278
+ // assign the system KV cache to all parallel sequences
279
+ for (int32_t i = 1; i <= n_clients; ++i) {
280
+ llama_memory_seq_cp(mem, 0, i, -1, -1);
281
+ }
282
+
283
+ LOG_INF("\n");
284
+ }
285
+
286
+ LOG_INF("Processing requests ...\n\n");
287
+
288
+ while (true) {
289
+ common_batch_clear(batch);
290
+
291
+ // decode any currently ongoing sequences
292
+ for (auto & client : clients) {
293
+ if (client.seq_id == -1) {
294
+ continue;
295
+ }
296
+
297
+ client.i_batch = batch.n_tokens;
298
+
299
+ common_batch_add(batch, client.sampled, client.n_past++, { client.id + 1 }, true);
300
+
301
+ client.n_decoded += 1;
302
+ }
303
+
304
+ if (batch.n_tokens == 0) {
305
+ // all sequences have ended - clear the entire KV cache
306
+ for (int i = 1; i <= n_clients; ++i) {
307
+ llama_memory_seq_rm(mem, i, -1, -1);
308
+ // but keep the system prompt
309
+ llama_memory_seq_cp(mem, 0, i, -1, -1);
310
+ }
311
+
312
+ LOG_INF("%s: clearing the KV cache\n", __func__);
313
+ }
314
+
315
+ // insert new sequences for decoding
316
+ if (cont_batching || batch.n_tokens == 0) {
317
+ for (auto & client : clients) {
318
+ if (client.seq_id == -1 && g_seq_id < n_seq) {
319
+ client.seq_id = g_seq_id;
320
+
321
+ client.t_start_prompt = ggml_time_us();
322
+ client.t_start_gen = 0;
323
+
324
+ client.input = k_prompts[rand() % k_prompts.size()];
325
+ client.response = "";
326
+
327
+ // construct the prompt:
328
+ // [system prompt] + [junk] + [user prompt]
329
+ client.n_past = 0;
330
+ client.prompt = "";
331
+ if (is_sp_shared) {
332
+ client.n_past = n_tokens_system;
333
+ } else {
334
+ client.prompt += k_system;
335
+ }
336
+
337
+ const int n_junk_cur = rand() % n_junk;
338
+
339
+ for (int i = 0; i < n_junk_cur; ++i) {
340
+ const int r = rand() % k_questions.size();
341
+ client.prompt += "User:\n" + k_questions[r] + "\nAssistant:\n " + k_answers[r] + "\n";
342
+ }
343
+ client.prompt += "User:\n" + client.input + "\nAssistant:\n";
344
+
345
+ common_sampler_reset(client.smpl);
346
+
347
+ // do not prepend BOS because we have a system prompt!
348
+ std::vector<llama_token> tokens_prompt;
349
+ tokens_prompt = common_tokenize(ctx, client.prompt, false);
350
+
351
+ for (size_t i = 0; i < tokens_prompt.size(); ++i) {
352
+ common_batch_add(batch, tokens_prompt[i], client.n_past++, { client.id + 1 }, false);
353
+ }
354
+
355
+ // extract the logits only for the last token
356
+ if (batch.n_tokens > 0) {
357
+ batch.logits[batch.n_tokens - 1] = true;
358
+ }
359
+
360
+ client.n_prompt = tokens_prompt.size();
361
+ client.n_decoded = 0;
362
+ client.i_batch = batch.n_tokens - 1;
363
+
364
+ LOG_INF("\033[31mClient %3d, seq %4d, junk = %4d, prompt = %d, started decoding ...\033[0m\n", client.id, client.seq_id, n_junk_cur, client.n_prompt);
365
+
366
+ g_seq_id += 1;
367
+
368
+ // insert new requests one-by-one
369
+ //if (cont_batching) {
370
+ // break;
371
+ //}
372
+ }
373
+ }
374
+ }
375
+
376
+ if (batch.n_tokens == 0) {
377
+ break;
378
+ }
379
+
380
+ // process in chunks of params.n_batch
381
+ int32_t n_batch = params.n_batch;
382
+
383
+ int32_t i_next = 0;
384
+
385
+ for (int32_t i = 0; i < batch.n_tokens; i = i_next) {
386
+ // experiment: process in powers of 2
387
+ //if (i + n_batch > (int32_t) batch.n_tokens && n_batch > 32) {
388
+ // n_batch /= 2;
389
+ // i -= n_batch;
390
+ // continue;
391
+ //}
392
+
393
+ const int32_t n_tokens = std::min(n_batch, batch.n_tokens - i);
394
+
395
+ llama_batch batch_view = {
396
+ n_tokens,
397
+ batch.token + i,
398
+ nullptr,
399
+ batch.pos + i,
400
+ batch.n_seq_id + i,
401
+ batch.seq_id + i,
402
+ batch.logits + i,
403
+ };
404
+
405
+ const int ret = llama_decode(ctx, batch_view);
406
+ if (ret != 0) {
407
+ if (n_batch == 1 || ret < 0) {
408
+ // if you get here, it means the KV cache is full - try increasing it via the context size
409
+ LOG_ERR("%s : failed to decode the batch, n_batch = %d, ret = %d\n", __func__, n_batch, ret);
410
+ return 1;
411
+ }
412
+
413
+ LOG_WRN("%s : failed to decode the batch, retrying with n_batch = %d\n", __func__, n_batch / 2);
414
+
415
+ n_cache_miss += 1;
416
+
417
+ // retry with half the batch size to try to find a free slot in the KV cache
418
+ n_batch /= 2;
419
+
420
+ continue;
421
+ }
422
+
423
+ LOG_DBG("%s : decoded batch of %d tokens\n", __func__, n_tokens);
424
+
425
+ // move the head of the batch forward with the number of tokens we just processed
426
+ i_next = i + n_tokens;
427
+
428
+ // on successful decode, restore the original batch size
429
+ n_batch = params.n_batch;
430
+
431
+ for (auto & client : clients) {
432
+ if (client.i_batch < (int) i || client.i_batch >= (int) (i + n_tokens)) {
433
+ continue;
434
+ }
435
+
436
+ //printf("client %d, seq %d, token %d, pos %d, batch %d\n",
437
+ // client.id, client.seq_id, client.sampled, client.n_decoded, client.i_batch);
438
+
439
+ const llama_token id = common_sampler_sample(client.smpl, ctx, client.i_batch - i);
440
+
441
+ common_sampler_accept(client.smpl, id, true);
442
+
443
+ if (client.n_decoded == 1) {
444
+ // start measuring generation time after the first token to make sure all concurrent clients
445
+ // have their prompt already processed
446
+ client.t_start_gen = ggml_time_us();
447
+ }
448
+
449
+ const std::string token_str = common_token_to_piece(ctx, id);
450
+
451
+ client.response += token_str;
452
+ client.sampled = id;
453
+
454
+ //printf("client %d, seq %d, token %d, pos %d, batch %d: %s\n",
455
+ // client.id, client.seq_id, id, client.n_decoded, client.i_batch, token_str.c_str());
456
+
457
+ if (client.n_decoded > 2 &&
458
+ (llama_vocab_is_eog(vocab, id) ||
459
+ (params.n_predict > 0 && client.n_decoded >= params.n_predict) ||
460
+ client.response.find("User:") != std::string::npos)) {
461
+ // basic reverse prompt
462
+ const size_t pos = client.response.find("User:");
463
+ if (pos != std::string::npos) {
464
+ client.response = client.response.substr(0, pos);
465
+ }
466
+
467
+ // delete only the generated part of the sequence, i.e. keep the system prompt in the cache
468
+ llama_memory_seq_rm(mem, client.id + 1, -1, -1);
469
+ llama_memory_seq_cp(mem, 0, client.id + 1, -1, -1);
470
+
471
+ const auto t_main_end = ggml_time_us();
472
+
473
+ LOG_INF("\033[31mClient %3d, seq %3d/%3d, prompt %4d t, response %4d t, time %5.2f s, speed %5.2f t/s, cache miss %d \033[0m \n\nInput: %s\n\033[35mResponse: %s\033[0m\n\n",
474
+ client.id, client.seq_id, n_seq, client.n_prompt, client.n_decoded,
475
+ (t_main_end - client.t_start_prompt) / 1e6,
476
+ (double) (client.n_prompt + client.n_decoded) / (t_main_end - client.t_start_prompt) * 1e6,
477
+ n_cache_miss,
478
+ ::trim(client.input).c_str(),
479
+ ::trim(client.response).c_str());
480
+
481
+ n_total_prompt += client.n_prompt;
482
+ n_total_gen += client.n_decoded;
483
+
484
+ client.seq_id = -1;
485
+ }
486
+
487
+ client.i_batch = -1;
488
+ }
489
+ }
490
+ }
491
+
492
+ const auto t_main_end = ggml_time_us();
493
+
494
+ print_date_time();
495
+
496
+ LOG_INF("%s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d\n", __func__, n_clients, n_seq, cont_batching, n_tokens_system);
497
+ if (params.prompt_file.empty()) {
498
+ params.prompt_file = "used built-in defaults";
499
+ }
500
+ LOG_INF("External prompt file: \033[32m%s\033[0m\n", params.prompt_file.c_str());
501
+ LOG_INF("Model and path used: \033[32m%s\033[0m\n\n", params.model.path.c_str());
502
+
503
+ LOG_INF("Total prompt tokens: %6d, speed: %5.2f t/s\n", n_total_prompt, (double) (n_total_prompt ) / (t_main_end - t_main_start) * 1e6);
504
+ LOG_INF("Total gen tokens: %6d, speed: %5.2f t/s\n", n_total_gen, (double) (n_total_gen ) / (t_main_end - t_main_start) * 1e6);
505
+ LOG_INF("Total speed (AVG): %6s speed: %5.2f t/s\n", "", (double) (n_total_prompt + n_total_gen) / (t_main_end - t_main_start) * 1e6);
506
+ LOG_INF("Cache misses: %6d\n", n_cache_miss);
507
+
508
+ LOG_INF("\n");
509
+
510
+ // TODO: print sampling/grammar timings for all clients
511
+ llama_perf_context_print(ctx);
512
+
513
+ llama_batch_free(batch);
514
+
515
+ llama_backend_free();
516
+
517
+ LOG("\n\n");
518
+
519
+ return 0;
520
+ }
backend/llama.cpp/examples/passkey/CMakeLists.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ set(TARGET llama-passkey)
2
+ add_executable(${TARGET} passkey.cpp)
3
+ install(TARGETS ${TARGET} RUNTIME)
4
+ target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
5
+ target_compile_features(${TARGET} PRIVATE cxx_std_17)
backend/llama.cpp/examples/passkey/README.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # llama.cpp/example/passkey
2
+
3
+ A passkey retrieval task is an evaluation method used to measure a language
4
+ models ability to recall information from long contexts.
5
+
6
+ See the following PRs for more info:
7
+
8
+ - https://github.com/ggml-org/llama.cpp/pull/3856
9
+ - https://github.com/ggml-org/llama.cpp/pull/4810
10
+
11
+ ### Usage
12
+
13
+ ```bash
14
+ llama-passkey -m ./models/llama-7b-v2/ggml-model-f16.gguf --junk 250
15
+ ```
backend/llama.cpp/examples/passkey/passkey.cpp ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "arg.h"
2
+ #include "common.h"
3
+ #include "log.h"
4
+ #include "llama.h"
5
+
6
+ #include <clocale>
7
+ #include <cmath>
8
+ #include <cstdio>
9
+ #include <string>
10
+ #include <vector>
11
+ #include <algorithm>
12
+
13
+ static void print_usage(int, char ** argv) {
14
+ LOG("\nexample usage:\n");
15
+ LOG("\n %s -m model.gguf --junk 250 --pos 90 --keep 32 --grp-attn-n 2 [--seed 1234]\n", argv[0]);
16
+ LOG("\n");
17
+ }
18
+
19
+ int main(int argc, char ** argv) {
20
+ std::setlocale(LC_NUMERIC, "C");
21
+
22
+ common_params params;
23
+
24
+ params.n_junk = 250;
25
+ params.n_keep = 32;
26
+ params.i_pos = -1;
27
+
28
+ common_init();
29
+
30
+ if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_PASSKEY, print_usage)) {
31
+ return 1;
32
+ }
33
+
34
+ int n_junk = params.n_junk;
35
+ int n_keep = params.n_keep;
36
+ int n_grp = params.grp_attn_n;
37
+ int i_pos = params.i_pos;
38
+
39
+ if (i_pos == -1) {
40
+ i_pos = rand() % n_junk;
41
+ }
42
+
43
+ const std::string prompt_prefix = "There is an important info hidden inside a lot of irrelevant text. Find it and memorize them. I will quiz you about the important information there.";
44
+ const std::string prompt_suffix = " What is the pass key? The pass key is";
45
+
46
+ // generate junk text
47
+ params.prompt = prompt_prefix;
48
+
49
+ const int passkey = rand() % 50000 + 1;
50
+
51
+ for (int i = 0; i < n_junk; i++) {
52
+ if (i % n_junk == i_pos) {
53
+ params.prompt += " The pass key is " + std::to_string(passkey) + ". Remember it. " + std::to_string(passkey) + " is the pass key.";
54
+ }
55
+
56
+ params.prompt += " The grass is green. The sky is blue. The sun is yellow. Here we go. There and back again.";
57
+ }
58
+
59
+ params.prompt += prompt_suffix;
60
+
61
+ // init LLM
62
+
63
+ llama_backend_init();
64
+ llama_numa_init(params.numa);
65
+
66
+ // initialize the model
67
+
68
+ llama_model_params model_params = common_model_params_to_llama(params);
69
+
70
+ llama_model * model = llama_model_load_from_file(params.model.path.c_str(), model_params);
71
+
72
+ if (model == NULL) {
73
+ LOG_ERR("%s: unable to load model\n" , __func__);
74
+ return 1;
75
+ }
76
+
77
+ const llama_vocab * vocab = llama_model_get_vocab(model);
78
+
79
+ // initialize the context
80
+
81
+ llama_context_params ctx_params = common_context_params_to_llama(params);
82
+
83
+ ctx_params.n_ctx = llama_model_n_ctx_train(model)*n_grp + n_keep;
84
+
85
+ GGML_ASSERT(ctx_params.n_batch % n_grp == 0 && "n_batch must be divisible by n_grp");
86
+
87
+ llama_context * ctx = llama_init_from_model(model, ctx_params);
88
+ if (ctx == NULL) {
89
+ LOG_ERR("%s: failed to create the llama_context\n" , __func__);
90
+ return 1;
91
+ }
92
+
93
+ auto sparams = llama_sampler_chain_default_params();
94
+
95
+ llama_sampler * smpl = llama_sampler_chain_init(sparams);
96
+
97
+ llama_sampler_chain_add(smpl, llama_sampler_init_greedy());
98
+
99
+ // tokenize the prompt
100
+ std::vector<llama_token> tokens_list;
101
+ tokens_list = common_tokenize(ctx, params.prompt, true);
102
+
103
+ // tokenize the prefix and use it as a sink
104
+ const int n_tokens_prefix = common_tokenize(ctx, prompt_prefix, true).size();
105
+
106
+ const int n_tokens_all = tokens_list.size();
107
+
108
+ // we leave a margin of 16 tokens for the generated text - it should contain just the passkey
109
+ const int n_predict = 16;
110
+
111
+ // total length of the sequences including the prompt
112
+ const int n_len = n_tokens_all + n_predict;
113
+
114
+ const int n_ctx = llama_n_ctx(ctx) - n_keep;
115
+ const int n_kv_req = llama_n_ctx(ctx);
116
+ const int n_batch = ctx_params.n_batch;
117
+ const int n_batch_grp = ctx_params.n_batch/n_grp;
118
+
119
+ LOG_INF("\n%s: n_len = %d, n_ctx = %d, n_kv_req = %d, n_grp = %d, n_batch = %d, n_junk = %d, i_pos = %d\n", __func__, n_len, n_ctx, n_kv_req, n_grp, n_batch, n_junk, i_pos);
120
+
121
+ // print the prompt token-by-token
122
+
123
+ LOG_INF("\n");
124
+ LOG_INF("prefix tokens: %d\n", n_tokens_prefix);
125
+ LOG_INF("prompt tokens: %d\n", n_tokens_all);
126
+ //LOG_INF("prompt: %s\n", params.prompt.c_str());
127
+
128
+ llama_batch batch = llama_batch_init(params.n_batch, 0, 1);
129
+
130
+ int n_past = 0;
131
+
132
+ auto * mem = llama_get_memory(ctx);
133
+
134
+ // fill the KV cache
135
+ for (int i = 0; i < n_ctx; i += n_batch) {
136
+ if (i > 0 && n_grp > 1) {
137
+ // if SelfExtend is enabled, we compress the position from the last batch by a factor of n_grp
138
+ const int ib = i/n_batch - 1;
139
+ const int bd = n_batch_grp*(n_grp - 1);
140
+
141
+ llama_memory_seq_add(mem, 0, n_past - n_batch, n_past, ib*bd);
142
+ llama_memory_seq_div(mem, 0, n_past - n_batch + ib*bd, n_past + ib*bd, n_grp);
143
+
144
+ n_past = llama_memory_seq_pos_max(mem, 0) + 1;
145
+ }
146
+
147
+ common_batch_clear(batch);
148
+
149
+ for (int j = 0; j < n_batch && i + j < n_tokens_all; j++) {
150
+ common_batch_add(batch, tokens_list[i + j], n_past++, { 0 }, false);
151
+ }
152
+
153
+ if (i + n_batch >= n_tokens_all) {
154
+ batch.logits[batch.n_tokens - 1] = true;
155
+ }
156
+
157
+ if (llama_decode(ctx, batch) != 0) {
158
+ LOG_INF("%s: llama_decode() failed\n", __func__);
159
+ return 1;
160
+ }
161
+
162
+ LOG_INF("%s: processed: [%6d, %6d)\n", __func__, i, std::min(i + n_batch, n_tokens_all));
163
+
164
+ if (i + n_batch >= n_tokens_all) {
165
+ break;
166
+ }
167
+ }
168
+
169
+ for (int i = n_ctx; i < n_tokens_all; i += n_batch) {
170
+ const int n_discard = n_batch;
171
+
172
+ LOG_INF("%s: shifting KV cache with %d\n", __func__, n_discard);
173
+
174
+ llama_memory_seq_rm (mem, 0, n_keep , n_keep + n_discard);
175
+ llama_memory_seq_add(mem, 0, n_keep + n_discard, n_ctx, -n_discard);
176
+
177
+ n_past = llama_memory_seq_pos_max(mem, 0) + 1;
178
+
179
+ common_batch_clear(batch);
180
+
181
+ for (int j = 0; j < n_batch && i + j < n_tokens_all; j++) {
182
+ common_batch_add(batch, tokens_list[i + j], n_past++, { 0 }, false);
183
+ }
184
+
185
+ if (i + n_batch >= n_tokens_all) {
186
+ batch.logits[batch.n_tokens - 1] = true;
187
+ }
188
+
189
+ if (llama_decode(ctx, batch) != 0) {
190
+ LOG_ERR("%s: llama_decode() failed\n", __func__);
191
+ return 1;
192
+ }
193
+
194
+ LOG_INF("%s: processed: [%6d, %6d)\n", __func__, i, std::min(i + n_batch, n_tokens_all));
195
+ }
196
+
197
+ {
198
+ const int n_discard = n_past - n_ctx + n_predict;
199
+
200
+ if (n_discard > 0) {
201
+ LOG_INF("%s: shifting KV cache with %d to free space for the answer\n", __func__, n_discard);
202
+
203
+ llama_memory_seq_rm (mem, 0, n_keep , n_keep + n_discard);
204
+ llama_memory_seq_add(mem, 0, n_keep + n_discard, n_ctx, -n_discard);
205
+
206
+ n_past = llama_memory_seq_pos_max(mem, 0) + 1;
207
+ }
208
+ }
209
+
210
+ LOG_INF("\n");
211
+ LOG_INF("%s: passkey = %d, inserted at position %d / %d (token pos: ~%d)\n", __func__, passkey, i_pos, n_junk, (i_pos * n_tokens_all) / n_junk);
212
+ LOG_INF("\n");
213
+
214
+ // main loop
215
+
216
+ int n_cur = n_tokens_all;
217
+ int n_decode = 0;
218
+
219
+ LOG_INF("%s", prompt_suffix.c_str());
220
+
221
+ const auto t_main_start = ggml_time_us();
222
+
223
+ while (n_cur <= n_len) {
224
+ // sample the next token
225
+ {
226
+ const llama_token new_token_id = llama_sampler_sample(smpl, ctx, batch.n_tokens - 1);
227
+
228
+ // is it an end of generation?
229
+ if (llama_vocab_is_eog(vocab, new_token_id) || n_cur == n_len) {
230
+ LOG("\n");
231
+
232
+ break;
233
+ }
234
+
235
+ LOG("%s", common_token_to_piece(ctx, new_token_id).c_str());
236
+
237
+ n_decode += 1;
238
+
239
+ // prepare the next batch
240
+ common_batch_clear(batch);
241
+
242
+ // push this new token for next evaluation
243
+ common_batch_add(batch, new_token_id, n_past++, { 0 }, true);
244
+ }
245
+
246
+ n_cur += 1;
247
+
248
+ // evaluate the current batch with the transformer model
249
+ if (llama_decode(ctx, batch)) {
250
+ LOG_ERR("%s : failed to eval, return code %d\n", __func__, 1);
251
+ return 1;
252
+ }
253
+ }
254
+
255
+ LOG("\n");
256
+
257
+ const auto t_main_end = ggml_time_us();
258
+
259
+ LOG_INF("%s: decoded %d tokens in %.2f s, speed: %.2f t/s\n",
260
+ __func__, n_decode, (t_main_end - t_main_start) / 1000000.0f, n_decode / ((t_main_end - t_main_start) / 1000000.0f));
261
+
262
+ LOG("\n");
263
+ llama_perf_context_print(ctx);
264
+
265
+ LOG("\n");
266
+
267
+ llama_sampler_free(smpl);
268
+
269
+ llama_batch_free(batch);
270
+
271
+ llama_free(ctx);
272
+ llama_model_free(model);
273
+
274
+ llama_backend_free();
275
+
276
+ return 0;
277
+ }
backend/llama.cpp/examples/pydantic_models_to_grammar.py ADDED
@@ -0,0 +1,1322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ import json
5
+ import re
6
+ from copy import copy
7
+ from enum import Enum
8
+ from inspect import getdoc, isclass
9
+ from typing import TYPE_CHECKING, Any, Callable, Optional, Union, get_args, get_origin, get_type_hints
10
+
11
+ from docstring_parser import parse
12
+ from pydantic import BaseModel, create_model
13
+
14
+ if TYPE_CHECKING:
15
+ from types import GenericAlias
16
+ else:
17
+ # python 3.8 compat
18
+ from typing import _GenericAlias as GenericAlias
19
+
20
+ # TODO: fix this
21
+ # pyright: reportAttributeAccessIssue=information
22
+
23
+
24
+ class PydanticDataType(Enum):
25
+ """
26
+ Defines the data types supported by the grammar_generator.
27
+
28
+ Attributes:
29
+ STRING (str): Represents a string data type.
30
+ BOOLEAN (str): Represents a boolean data type.
31
+ INTEGER (str): Represents an integer data type.
32
+ FLOAT (str): Represents a float data type.
33
+ OBJECT (str): Represents an object data type.
34
+ ARRAY (str): Represents an array data type.
35
+ ENUM (str): Represents an enum data type.
36
+ CUSTOM_CLASS (str): Represents a custom class data type.
37
+ """
38
+
39
+ STRING = "string"
40
+ TRIPLE_QUOTED_STRING = "triple_quoted_string"
41
+ MARKDOWN_CODE_BLOCK = "markdown_code_block"
42
+ BOOLEAN = "boolean"
43
+ INTEGER = "integer"
44
+ FLOAT = "float"
45
+ OBJECT = "object"
46
+ ARRAY = "array"
47
+ ENUM = "enum"
48
+ ANY = "any"
49
+ NULL = "null"
50
+ CUSTOM_CLASS = "custom-class"
51
+ CUSTOM_DICT = "custom-dict"
52
+ SET = "set"
53
+
54
+
55
+ def map_pydantic_type_to_gbnf(pydantic_type: type[Any]) -> str:
56
+ origin_type = get_origin(pydantic_type)
57
+ origin_type = pydantic_type if origin_type is None else origin_type
58
+
59
+ if isclass(origin_type) and issubclass(origin_type, str):
60
+ return PydanticDataType.STRING.value
61
+ elif isclass(origin_type) and issubclass(origin_type, bool):
62
+ return PydanticDataType.BOOLEAN.value
63
+ elif isclass(origin_type) and issubclass(origin_type, int):
64
+ return PydanticDataType.INTEGER.value
65
+ elif isclass(origin_type) and issubclass(origin_type, float):
66
+ return PydanticDataType.FLOAT.value
67
+ elif isclass(origin_type) and issubclass(origin_type, Enum):
68
+ return PydanticDataType.ENUM.value
69
+
70
+ elif isclass(origin_type) and issubclass(origin_type, BaseModel):
71
+ return format_model_and_field_name(origin_type.__name__)
72
+ elif origin_type is list:
73
+ element_type = get_args(pydantic_type)[0]
74
+ return f"{map_pydantic_type_to_gbnf(element_type)}-list"
75
+ elif origin_type is set:
76
+ element_type = get_args(pydantic_type)[0]
77
+ return f"{map_pydantic_type_to_gbnf(element_type)}-set"
78
+ elif origin_type is Union:
79
+ union_types = get_args(pydantic_type)
80
+ union_rules = [map_pydantic_type_to_gbnf(ut) for ut in union_types]
81
+ return f"union-{'-or-'.join(union_rules)}"
82
+ elif origin_type is Optional:
83
+ element_type = get_args(pydantic_type)[0]
84
+ return f"optional-{map_pydantic_type_to_gbnf(element_type)}"
85
+ elif isclass(origin_type):
86
+ return f"{PydanticDataType.CUSTOM_CLASS.value}-{format_model_and_field_name(origin_type.__name__)}"
87
+ elif origin_type is dict:
88
+ key_type, value_type = get_args(pydantic_type)
89
+ return f"custom-dict-key-type-{format_model_and_field_name(map_pydantic_type_to_gbnf(key_type))}-value-type-{format_model_and_field_name(map_pydantic_type_to_gbnf(value_type))}"
90
+ else:
91
+ return "unknown"
92
+
93
+
94
+ def format_model_and_field_name(model_name: str) -> str:
95
+ parts = re.findall("[A-Z][^A-Z]*", model_name)
96
+ if not parts: # Check if the list is empty
97
+ return model_name.lower().replace("_", "-")
98
+ return "-".join(part.lower().replace("_", "-") for part in parts)
99
+
100
+
101
+ def generate_list_rule(element_type):
102
+ """
103
+ Generate a GBNF rule for a list of a given element type.
104
+
105
+ :param element_type: The type of the elements in the list (e.g., 'string').
106
+ :return: A string representing the GBNF rule for a list of the given type.
107
+ """
108
+ rule_name = f"{map_pydantic_type_to_gbnf(element_type)}-list"
109
+ element_rule = map_pydantic_type_to_gbnf(element_type)
110
+ list_rule = rf'{rule_name} ::= "[" {element_rule} ("," {element_rule})* "]"'
111
+ return list_rule
112
+
113
+
114
+ def get_members_structure(cls, rule_name):
115
+ if issubclass(cls, Enum):
116
+ # Handle Enum types
117
+ members = [f'"\\"{member.value}\\""' for name, member in cls.__members__.items()]
118
+ return f"{cls.__name__.lower()} ::= " + " | ".join(members)
119
+ if cls.__annotations__ and cls.__annotations__ != {}:
120
+ result = f'{rule_name} ::= "{{"'
121
+ # Modify this comprehension
122
+ members = [
123
+ f' "\\"{name}\\"" ":" {map_pydantic_type_to_gbnf(param_type)}'
124
+ for name, param_type in get_type_hints(cls).items()
125
+ if name != "self"
126
+ ]
127
+
128
+ result += '"," '.join(members)
129
+ result += ' "}"'
130
+ return result
131
+ if rule_name == "custom-class-any":
132
+ result = f"{rule_name} ::= "
133
+ result += "value"
134
+ return result
135
+
136
+ init_signature = inspect.signature(cls.__init__)
137
+ parameters = init_signature.parameters
138
+ result = f'{rule_name} ::= "{{"'
139
+ # Modify this comprehension too
140
+ members = [
141
+ f' "\\"{name}\\"" ":" {map_pydantic_type_to_gbnf(param.annotation)}'
142
+ for name, param in parameters.items()
143
+ if name != "self" and param.annotation != inspect.Parameter.empty
144
+ ]
145
+
146
+ result += '", "'.join(members)
147
+ result += ' "}"'
148
+ return result
149
+
150
+
151
+ def regex_to_gbnf(regex_pattern: str) -> str:
152
+ """
153
+ Translate a basic regex pattern to a GBNF rule.
154
+ Note: This function handles only a subset of simple regex patterns.
155
+ """
156
+ gbnf_rule = regex_pattern
157
+
158
+ # Translate common regex components to GBNF
159
+ gbnf_rule = gbnf_rule.replace("\\d", "[0-9]")
160
+ gbnf_rule = gbnf_rule.replace("\\s", "[ \t\n]")
161
+
162
+ # Handle quantifiers and other regex syntax that is similar in GBNF
163
+ # (e.g., '*', '+', '?', character classes)
164
+
165
+ return gbnf_rule
166
+
167
+
168
+ def generate_gbnf_integer_rules(max_digit=None, min_digit=None):
169
+ """
170
+
171
+ Generate GBNF Integer Rules
172
+
173
+ Generates GBNF (Generalized Backus-Naur Form) rules for integers based on the given maximum and minimum digits.
174
+
175
+ Parameters:
176
+ max_digit (int): The maximum number of digits for the integer. Default is None.
177
+ min_digit (int): The minimum number of digits for the integer. Default is None.
178
+
179
+ Returns:
180
+ integer_rule (str): The identifier for the integer rule generated.
181
+ additional_rules (list): A list of additional rules generated based on the given maximum and minimum digits.
182
+
183
+ """
184
+ additional_rules = []
185
+
186
+ # Define the rule identifier based on max_digit and min_digit
187
+ integer_rule = "integer-part"
188
+ if max_digit is not None:
189
+ integer_rule += f"-max{max_digit}"
190
+ if min_digit is not None:
191
+ integer_rule += f"-min{min_digit}"
192
+
193
+ # Handling Integer Rules
194
+ if max_digit is not None or min_digit is not None:
195
+ # Start with an empty rule part
196
+ integer_rule_part = ""
197
+
198
+ # Add mandatory digits as per min_digit
199
+ if min_digit is not None:
200
+ integer_rule_part += "[0-9] " * min_digit
201
+
202
+ # Add optional digits up to max_digit
203
+ if max_digit is not None:
204
+ optional_digits = max_digit - (min_digit if min_digit is not None else 0)
205
+ integer_rule_part += "".join(["[0-9]? " for _ in range(optional_digits)])
206
+
207
+ # Trim the rule part and append it to additional rules
208
+ integer_rule_part = integer_rule_part.strip()
209
+ if integer_rule_part:
210
+ additional_rules.append(f"{integer_rule} ::= {integer_rule_part}")
211
+
212
+ return integer_rule, additional_rules
213
+
214
+
215
+ def generate_gbnf_float_rules(max_digit=None, min_digit=None, max_precision=None, min_precision=None):
216
+ """
217
+ Generate GBNF float rules based on the given constraints.
218
+
219
+ :param max_digit: Maximum number of digits in the integer part (default: None)
220
+ :param min_digit: Minimum number of digits in the integer part (default: None)
221
+ :param max_precision: Maximum number of digits in the fractional part (default: None)
222
+ :param min_precision: Minimum number of digits in the fractional part (default: None)
223
+ :return: A tuple containing the float rule and additional rules as a list
224
+
225
+ Example Usage:
226
+ max_digit = 3
227
+ min_digit = 1
228
+ max_precision = 2
229
+ min_precision = 1
230
+ generate_gbnf_float_rules(max_digit, min_digit, max_precision, min_precision)
231
+
232
+ Output:
233
+ ('float-3-1-2-1', ['integer-part-max3-min1 ::= [0-9] [0-9] [0-9]?', 'fractional-part-max2-min1 ::= [0-9] [0-9]?', 'float-3-1-2-1 ::= integer-part-max3-min1 "." fractional-part-max2-min
234
+ *1'])
235
+
236
+ Note:
237
+ GBNF stands for Generalized Backus-Naur Form, which is a notation technique to specify the syntax of programming languages or other formal grammars.
238
+ """
239
+ additional_rules = []
240
+
241
+ # Define the integer part rule
242
+ integer_part_rule = (
243
+ "integer-part"
244
+ + (f"-max{max_digit}" if max_digit is not None else "")
245
+ + (f"-min{min_digit}" if min_digit is not None else "")
246
+ )
247
+
248
+ # Define the fractional part rule based on precision constraints
249
+ fractional_part_rule = "fractional-part"
250
+ fractional_rule_part = ""
251
+ if max_precision is not None or min_precision is not None:
252
+ fractional_part_rule += (f"-max{max_precision}" if max_precision is not None else "") + (
253
+ f"-min{min_precision}" if min_precision is not None else ""
254
+ )
255
+ # Minimum number of digits
256
+ fractional_rule_part = "[0-9]" * (min_precision if min_precision is not None else 1)
257
+ # Optional additional digits
258
+ fractional_rule_part += "".join(
259
+ [" [0-9]?"] * ((max_precision - (
260
+ min_precision if min_precision is not None else 1)) if max_precision is not None else 0)
261
+ )
262
+ additional_rules.append(f"{fractional_part_rule} ::= {fractional_rule_part}")
263
+
264
+ # Define the float rule
265
+ float_rule = f"float-{max_digit if max_digit is not None else 'X'}-{min_digit if min_digit is not None else 'X'}-{max_precision if max_precision is not None else 'X'}-{min_precision if min_precision is not None else 'X'}"
266
+ additional_rules.append(f'{float_rule} ::= {integer_part_rule} "." {fractional_part_rule}')
267
+
268
+ # Generating the integer part rule definition, if necessary
269
+ if max_digit is not None or min_digit is not None:
270
+ integer_rule_part = "[0-9]"
271
+ if min_digit is not None and min_digit > 1:
272
+ integer_rule_part += " [0-9]" * (min_digit - 1)
273
+ if max_digit is not None:
274
+ integer_rule_part += "".join([" [0-9]?"] * (max_digit - (min_digit if min_digit is not None else 1)))
275
+ additional_rules.append(f"{integer_part_rule} ::= {integer_rule_part.strip()}")
276
+
277
+ return float_rule, additional_rules
278
+
279
+
280
+ def generate_gbnf_rule_for_type(
281
+ model_name, field_name, field_type, is_optional, processed_models, created_rules, field_info=None
282
+ ) -> tuple[str, list[str]]:
283
+ """
284
+ Generate GBNF rule for a given field type.
285
+
286
+ :param model_name: Name of the model.
287
+
288
+ :param field_name: Name of the field.
289
+ :param field_type: Type of the field.
290
+ :param is_optional: Whether the field is optional.
291
+ :param processed_models: List of processed models.
292
+ :param created_rules: List of created rules.
293
+ :param field_info: Additional information about the field (optional).
294
+
295
+ :return: Tuple containing the GBNF type and a list of additional rules.
296
+ :rtype: tuple[str, list]
297
+ """
298
+ rules = []
299
+
300
+ field_name = format_model_and_field_name(field_name)
301
+ gbnf_type = map_pydantic_type_to_gbnf(field_type)
302
+
303
+ origin_type = get_origin(field_type)
304
+ origin_type = field_type if origin_type is None else origin_type
305
+
306
+ if isclass(origin_type) and issubclass(origin_type, BaseModel):
307
+ nested_model_name = format_model_and_field_name(field_type.__name__)
308
+ nested_model_rules, _ = generate_gbnf_grammar(field_type, processed_models, created_rules)
309
+ rules.extend(nested_model_rules)
310
+ gbnf_type, rules = nested_model_name, rules
311
+ elif isclass(origin_type) and issubclass(origin_type, Enum):
312
+ enum_values = [f'"\\"{e.value}\\""' for e in field_type] # Adding escaped quotes
313
+ enum_rule = f"{model_name}-{field_name} ::= {' | '.join(enum_values)}"
314
+ rules.append(enum_rule)
315
+ gbnf_type, rules = model_name + "-" + field_name, rules
316
+ elif origin_type is list: # Array
317
+ element_type = get_args(field_type)[0]
318
+ element_rule_name, additional_rules = generate_gbnf_rule_for_type(
319
+ model_name, f"{field_name}-element", element_type, is_optional, processed_models, created_rules
320
+ )
321
+ rules.extend(additional_rules)
322
+ array_rule = f"""{model_name}-{field_name} ::= "[" ws {element_rule_name} ("," ws {element_rule_name})* "]" """
323
+ rules.append(array_rule)
324
+ gbnf_type, rules = model_name + "-" + field_name, rules
325
+
326
+ elif origin_type is set: # Array
327
+ element_type = get_args(field_type)[0]
328
+ element_rule_name, additional_rules = generate_gbnf_rule_for_type(
329
+ model_name, f"{field_name}-element", element_type, is_optional, processed_models, created_rules
330
+ )
331
+ rules.extend(additional_rules)
332
+ array_rule = f"""{model_name}-{field_name} ::= "[" ws {element_rule_name} ("," ws {element_rule_name})* "]" """
333
+ rules.append(array_rule)
334
+ gbnf_type, rules = model_name + "-" + field_name, rules
335
+
336
+ elif gbnf_type.startswith("custom-class-"):
337
+ rules.append(get_members_structure(field_type, gbnf_type))
338
+ elif gbnf_type.startswith("custom-dict-"):
339
+ key_type, value_type = get_args(field_type)
340
+
341
+ additional_key_type, additional_key_rules = generate_gbnf_rule_for_type(
342
+ model_name, f"{field_name}-key-type", key_type, is_optional, processed_models, created_rules
343
+ )
344
+ additional_value_type, additional_value_rules = generate_gbnf_rule_for_type(
345
+ model_name, f"{field_name}-value-type", value_type, is_optional, processed_models, created_rules
346
+ )
347
+ gbnf_type = rf'{gbnf_type} ::= "{{" ( {additional_key_type} ": " {additional_value_type} ("," "\n" ws {additional_key_type} ":" {additional_value_type})* )? "}}" '
348
+
349
+ rules.extend(additional_key_rules)
350
+ rules.extend(additional_value_rules)
351
+ elif gbnf_type.startswith("union-"):
352
+ union_types = get_args(field_type)
353
+ union_rules = []
354
+
355
+ for union_type in union_types:
356
+ if isinstance(union_type, GenericAlias):
357
+ union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
358
+ model_name, field_name, union_type, False, processed_models, created_rules
359
+ )
360
+ union_rules.append(union_gbnf_type)
361
+ rules.extend(union_rules_list)
362
+
363
+ elif not issubclass(union_type, type(None)):
364
+ union_gbnf_type, union_rules_list = generate_gbnf_rule_for_type(
365
+ model_name, field_name, union_type, False, processed_models, created_rules
366
+ )
367
+ union_rules.append(union_gbnf_type)
368
+ rules.extend(union_rules_list)
369
+
370
+ # Defining the union grammar rule separately
371
+ if len(union_rules) == 1:
372
+ union_grammar_rule = f"{model_name}-{field_name}-optional ::= {' | '.join(union_rules)} | null"
373
+ else:
374
+ union_grammar_rule = f"{model_name}-{field_name}-union ::= {' | '.join(union_rules)}"
375
+ rules.append(union_grammar_rule)
376
+ if len(union_rules) == 1:
377
+ gbnf_type = f"{model_name}-{field_name}-optional"
378
+ else:
379
+ gbnf_type = f"{model_name}-{field_name}-union"
380
+ elif isclass(origin_type) and issubclass(origin_type, str):
381
+ if field_info and hasattr(field_info, "json_schema_extra") and field_info.json_schema_extra is not None:
382
+ triple_quoted_string = field_info.json_schema_extra.get("triple_quoted_string", False)
383
+ markdown_string = field_info.json_schema_extra.get("markdown_code_block", False)
384
+
385
+ gbnf_type = PydanticDataType.TRIPLE_QUOTED_STRING.value if triple_quoted_string else PydanticDataType.STRING.value
386
+ gbnf_type = PydanticDataType.MARKDOWN_CODE_BLOCK.value if markdown_string else gbnf_type
387
+
388
+ elif field_info and hasattr(field_info, "pattern"):
389
+ # Convert regex pattern to grammar rule
390
+ regex_pattern = field_info.regex.pattern
391
+ gbnf_type = f"pattern-{field_name} ::= {regex_to_gbnf(regex_pattern)}"
392
+ else:
393
+ gbnf_type = PydanticDataType.STRING.value
394
+
395
+ elif (
396
+ isclass(origin_type)
397
+ and issubclass(origin_type, float)
398
+ and field_info
399
+ and hasattr(field_info, "json_schema_extra")
400
+ and field_info.json_schema_extra is not None
401
+ ):
402
+ # Retrieve precision attributes for floats
403
+ max_precision = (
404
+ field_info.json_schema_extra.get("max_precision") if field_info and hasattr(field_info,
405
+ "json_schema_extra") else None
406
+ )
407
+ min_precision = (
408
+ field_info.json_schema_extra.get("min_precision") if field_info and hasattr(field_info,
409
+ "json_schema_extra") else None
410
+ )
411
+ max_digits = field_info.json_schema_extra.get("max_digit") if field_info and hasattr(field_info,
412
+ "json_schema_extra") else None
413
+ min_digits = field_info.json_schema_extra.get("min_digit") if field_info and hasattr(field_info,
414
+ "json_schema_extra") else None
415
+
416
+ # Generate GBNF rule for float with given attributes
417
+ gbnf_type, rules = generate_gbnf_float_rules(
418
+ max_digit=max_digits, min_digit=min_digits, max_precision=max_precision, min_precision=min_precision
419
+ )
420
+
421
+ elif (
422
+ isclass(origin_type)
423
+ and issubclass(origin_type, int)
424
+ and field_info
425
+ and hasattr(field_info, "json_schema_extra")
426
+ and field_info.json_schema_extra is not None
427
+ ):
428
+ # Retrieve digit attributes for integers
429
+ max_digits = field_info.json_schema_extra.get("max_digit") if field_info and hasattr(field_info,
430
+ "json_schema_extra") else None
431
+ min_digits = field_info.json_schema_extra.get("min_digit") if field_info and hasattr(field_info,
432
+ "json_schema_extra") else None
433
+
434
+ # Generate GBNF rule for integer with given attributes
435
+ gbnf_type, rules = generate_gbnf_integer_rules(max_digit=max_digits, min_digit=min_digits)
436
+ else:
437
+ gbnf_type, rules = gbnf_type, []
438
+
439
+ return gbnf_type, rules
440
+
441
+
442
+ def generate_gbnf_grammar(model: type[BaseModel], processed_models: set[type[BaseModel]], created_rules: dict[str, list[str]]) -> tuple[list[str], bool]:
443
+ """
444
+
445
+ Generate GBnF Grammar
446
+
447
+ Generates a GBnF grammar for a given model.
448
+
449
+ :param model: A Pydantic model class to generate the grammar for. Must be a subclass of BaseModel.
450
+ :param processed_models: A set of already processed models to prevent infinite recursion.
451
+ :param created_rules: A dict containing already created rules to prevent duplicates.
452
+ :return: A list of GBnF grammar rules in string format. And two booleans indicating if an extra markdown or triple quoted string is in the grammar.
453
+ Example Usage:
454
+ ```
455
+ model = MyModel
456
+ processed_models = set()
457
+ created_rules = dict()
458
+
459
+ gbnf_grammar = generate_gbnf_grammar(model, processed_models, created_rules)
460
+ ```
461
+ """
462
+ if model in processed_models:
463
+ return [], False
464
+
465
+ processed_models.add(model)
466
+ model_name = format_model_and_field_name(model.__name__)
467
+
468
+ if not issubclass(model, BaseModel):
469
+ # For non-Pydantic classes, generate model_fields from __annotations__ or __init__
470
+ if hasattr(model, "__annotations__") and model.__annotations__:
471
+ model_fields = {name: (typ, ...) for name, typ in get_type_hints(model).items()}
472
+ else:
473
+ init_signature = inspect.signature(model.__init__)
474
+ parameters = init_signature.parameters
475
+ model_fields = {name: (param.annotation, param.default) for name, param in parameters.items() if
476
+ name != "self"}
477
+ else:
478
+ # For Pydantic models, use model_fields and check for ellipsis (required fields)
479
+ model_fields = get_type_hints(model)
480
+
481
+ model_rule_parts = []
482
+ nested_rules = []
483
+ has_markdown_code_block = False
484
+ has_triple_quoted_string = False
485
+ look_for_markdown_code_block = False
486
+ look_for_triple_quoted_string = False
487
+ for field_name, field_info in model_fields.items():
488
+ if not issubclass(model, BaseModel):
489
+ field_type, default_value = field_info
490
+ # Check if the field is optional (not required)
491
+ is_optional = (default_value is not inspect.Parameter.empty) and (default_value is not Ellipsis)
492
+ else:
493
+ field_type = field_info
494
+ field_info = model.model_fields[field_name]
495
+ is_optional = field_info.is_required is False and get_origin(field_type) is Optional
496
+ rule_name, additional_rules = generate_gbnf_rule_for_type(
497
+ model_name, format_model_and_field_name(field_name), field_type, is_optional, processed_models,
498
+ created_rules, field_info
499
+ )
500
+ look_for_markdown_code_block = True if rule_name == "markdown_code_block" else False
501
+ look_for_triple_quoted_string = True if rule_name == "triple_quoted_string" else False
502
+ if not look_for_markdown_code_block and not look_for_triple_quoted_string:
503
+ if rule_name not in created_rules:
504
+ created_rules[rule_name] = additional_rules
505
+ model_rule_parts.append(f' ws "\\"{field_name}\\"" ":" ws {rule_name}') # Adding escaped quotes
506
+ nested_rules.extend(additional_rules)
507
+ else:
508
+ has_triple_quoted_string = look_for_triple_quoted_string
509
+ has_markdown_code_block = look_for_markdown_code_block
510
+
511
+ fields_joined = r' "," "\n" '.join(model_rule_parts)
512
+ model_rule = rf'{model_name} ::= "{{" "\n" {fields_joined} "\n" ws "}}"'
513
+
514
+ has_special_string = False
515
+ if has_triple_quoted_string:
516
+ model_rule += '"\\n" ws "}"'
517
+ model_rule += '"\\n" triple-quoted-string'
518
+ has_special_string = True
519
+ if has_markdown_code_block:
520
+ model_rule += '"\\n" ws "}"'
521
+ model_rule += '"\\n" markdown-code-block'
522
+ has_special_string = True
523
+ all_rules = [model_rule] + nested_rules
524
+
525
+ return all_rules, has_special_string
526
+
527
+
528
+ def generate_gbnf_grammar_from_pydantic_models(
529
+ models: list[type[BaseModel]], outer_object_name: str | None = None, outer_object_content: str | None = None,
530
+ list_of_outputs: bool = False
531
+ ) -> str:
532
+ """
533
+ Generate GBNF Grammar from Pydantic Models.
534
+
535
+ This method takes a list of Pydantic models and uses them to generate a GBNF grammar string. The generated grammar string can be used for parsing and validating data using the generated
536
+ * grammar.
537
+
538
+ Args:
539
+ models (list[type[BaseModel]]): A list of Pydantic models to generate the grammar from.
540
+ outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
541
+ outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
542
+ list_of_outputs (str, optional): Allows a list of output objects
543
+ Returns:
544
+ str: The generated GBNF grammar string.
545
+
546
+ Examples:
547
+ models = [UserModel, PostModel]
548
+ grammar = generate_gbnf_grammar_from_pydantic(models)
549
+ print(grammar)
550
+ # Output:
551
+ # root ::= UserModel | PostModel
552
+ # ...
553
+ """
554
+ processed_models: set[type[BaseModel]] = set()
555
+ all_rules = []
556
+ created_rules: dict[str, list[str]] = {}
557
+ if outer_object_name is None:
558
+ for model in models:
559
+ model_rules, _ = generate_gbnf_grammar(model, processed_models, created_rules)
560
+ all_rules.extend(model_rules)
561
+
562
+ if list_of_outputs:
563
+ root_rule = r'root ::= (" "| "\n") "[" ws grammar-models ("," ws grammar-models)* ws "]"' + "\n"
564
+ else:
565
+ root_rule = r'root ::= (" "| "\n") grammar-models' + "\n"
566
+ root_rule += "grammar-models ::= " + " | ".join(
567
+ [format_model_and_field_name(model.__name__) for model in models])
568
+ all_rules.insert(0, root_rule)
569
+ return "\n".join(all_rules)
570
+ elif outer_object_name is not None:
571
+ if list_of_outputs:
572
+ root_rule = (
573
+ rf'root ::= (" "| "\n") "[" ws {format_model_and_field_name(outer_object_name)} ("," ws {format_model_and_field_name(outer_object_name)})* ws "]"'
574
+ + "\n"
575
+ )
576
+ else:
577
+ root_rule = f"root ::= {format_model_and_field_name(outer_object_name)}\n"
578
+
579
+ model_rule = (
580
+ rf'{format_model_and_field_name(outer_object_name)} ::= (" "| "\n") "{{" ws "\"{outer_object_name}\"" ":" ws grammar-models'
581
+ )
582
+
583
+ fields_joined = " | ".join(
584
+ [rf"{format_model_and_field_name(model.__name__)}-grammar-model" for model in models])
585
+
586
+ grammar_model_rules = f"\ngrammar-models ::= {fields_joined}"
587
+ mod_rules = []
588
+ for model in models:
589
+ mod_rule = rf"{format_model_and_field_name(model.__name__)}-grammar-model ::= "
590
+ mod_rule += (
591
+ rf'"\"{model.__name__}\"" "," ws "\"{outer_object_content}\"" ":" ws {format_model_and_field_name(model.__name__)}' + "\n"
592
+ )
593
+ mod_rules.append(mod_rule)
594
+ grammar_model_rules += "\n" + "\n".join(mod_rules)
595
+
596
+ for model in models:
597
+ model_rules, has_special_string = generate_gbnf_grammar(model, processed_models,
598
+ created_rules)
599
+
600
+ if not has_special_string:
601
+ model_rules[0] += r'"\n" ws "}"'
602
+
603
+ all_rules.extend(model_rules)
604
+
605
+ all_rules.insert(0, root_rule + model_rule + grammar_model_rules)
606
+ return "\n".join(all_rules)
607
+
608
+
609
+ def get_primitive_grammar(grammar):
610
+ """
611
+ Returns the needed GBNF primitive grammar for a given GBNF grammar string.
612
+
613
+ Args:
614
+ grammar (str): The string containing the GBNF grammar.
615
+
616
+ Returns:
617
+ str: GBNF primitive grammar string.
618
+ """
619
+ type_list: list[type[object]] = []
620
+ if "string-list" in grammar:
621
+ type_list.append(str)
622
+ if "boolean-list" in grammar:
623
+ type_list.append(bool)
624
+ if "integer-list" in grammar:
625
+ type_list.append(int)
626
+ if "float-list" in grammar:
627
+ type_list.append(float)
628
+ additional_grammar = [generate_list_rule(t) for t in type_list]
629
+ primitive_grammar = r"""
630
+ boolean ::= "true" | "false"
631
+ null ::= "null"
632
+ string ::= "\"" (
633
+ [^"\\] |
634
+ "\\" (["\\/bfnrt] | "u" [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F] [0-9a-fA-F])
635
+ )* "\"" ws
636
+ ws ::= ([ \t\n] ws)?
637
+ float ::= ("-"? ([0] | [1-9] [0-9]*)) ("." [0-9]+)? ([eE] [-+]? [0-9]+)? ws
638
+
639
+ integer ::= [0-9]+"""
640
+
641
+ any_block = ""
642
+ if "custom-class-any" in grammar:
643
+ any_block = """
644
+ value ::= object | array | string | number | boolean | null
645
+
646
+ object ::=
647
+ "{" ws (
648
+ string ":" ws value
649
+ ("," ws string ":" ws value)*
650
+ )? "}" ws
651
+
652
+ array ::=
653
+ "[" ws (
654
+ value
655
+ ("," ws value)*
656
+ )? "]" ws
657
+
658
+ number ::= integer | float"""
659
+
660
+ markdown_code_block_grammar = ""
661
+ if "markdown-code-block" in grammar:
662
+ markdown_code_block_grammar = r'''
663
+ markdown-code-block ::= opening-triple-ticks markdown-code-block-content closing-triple-ticks
664
+ markdown-code-block-content ::= ( [^`] | "`" [^`] | "`" "`" [^`] )*
665
+ opening-triple-ticks ::= "```" "python" "\n" | "```" "c" "\n" | "```" "cpp" "\n" | "```" "txt" "\n" | "```" "text" "\n" | "```" "json" "\n" | "```" "javascript" "\n" | "```" "css" "\n" | "```" "html" "\n" | "```" "markdown" "\n"
666
+ closing-triple-ticks ::= "```" "\n"'''
667
+
668
+ if "triple-quoted-string" in grammar:
669
+ markdown_code_block_grammar = r"""
670
+ triple-quoted-string ::= triple-quotes triple-quoted-string-content triple-quotes
671
+ triple-quoted-string-content ::= ( [^'] | "'" [^'] | "'" "'" [^'] )*
672
+ triple-quotes ::= "'''" """
673
+ return "\n" + "\n".join(additional_grammar) + any_block + primitive_grammar + markdown_code_block_grammar
674
+
675
+
676
+ def generate_markdown_documentation(
677
+ pydantic_models: list[type[BaseModel]], model_prefix="Model", fields_prefix="Fields",
678
+ documentation_with_field_description=True
679
+ ) -> str:
680
+ """
681
+ Generate markdown documentation for a list of Pydantic models.
682
+
683
+ Args:
684
+ pydantic_models (list[type[BaseModel]]): list of Pydantic model classes.
685
+ model_prefix (str): Prefix for the model section.
686
+ fields_prefix (str): Prefix for the fields section.
687
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
688
+
689
+ Returns:
690
+ str: Generated text documentation.
691
+ """
692
+ documentation = ""
693
+ pyd_models: list[tuple[type[BaseModel], bool]] = [(model, True) for model in pydantic_models]
694
+ for model, add_prefix in pyd_models:
695
+ if add_prefix:
696
+ documentation += f"{model_prefix}: {model.__name__}\n"
697
+ else:
698
+ documentation += f"Model: {model.__name__}\n"
699
+
700
+ # Handling multi-line model description with proper indentation
701
+
702
+ class_doc = getdoc(model)
703
+ base_class_doc = getdoc(BaseModel)
704
+ class_description = class_doc if class_doc and class_doc != base_class_doc else ""
705
+ if class_description != "":
706
+ documentation += " Description: "
707
+ documentation += format_multiline_description(class_description, 0) + "\n"
708
+
709
+ if add_prefix:
710
+ # Indenting the fields section
711
+ documentation += f" {fields_prefix}:\n"
712
+ else:
713
+ documentation += f" Fields:\n" # noqa: F541
714
+ if isclass(model) and issubclass(model, BaseModel):
715
+ for name, field_type in get_type_hints(model).items():
716
+ # if name == "markdown_code_block":
717
+ # continue
718
+ if get_origin(field_type) == list:
719
+ element_type = get_args(field_type)[0]
720
+ if isclass(element_type) and issubclass(element_type, BaseModel):
721
+ pyd_models.append((element_type, False))
722
+ if get_origin(field_type) == Union:
723
+ element_types = get_args(field_type)
724
+ for element_type in element_types:
725
+ if isclass(element_type) and issubclass(element_type, BaseModel):
726
+ pyd_models.append((element_type, False))
727
+ documentation += generate_field_markdown(
728
+ name, field_type, model, documentation_with_field_description=documentation_with_field_description
729
+ )
730
+ documentation += "\n"
731
+
732
+ if hasattr(model, "Config") and hasattr(model.Config,
733
+ "json_schema_extra") and "example" in model.Config.json_schema_extra:
734
+ documentation += f" Expected Example Output for {format_model_and_field_name(model.__name__)}:\n"
735
+ json_example = json.dumps(model.Config.json_schema_extra["example"])
736
+ documentation += format_multiline_description(json_example, 2) + "\n"
737
+
738
+ return documentation
739
+
740
+
741
+ def generate_field_markdown(
742
+ field_name: str, field_type: type[Any], model: type[BaseModel], depth=1,
743
+ documentation_with_field_description=True
744
+ ) -> str:
745
+ """
746
+ Generate markdown documentation for a Pydantic model field.
747
+
748
+ Args:
749
+ field_name (str): Name of the field.
750
+ field_type (type[Any]): Type of the field.
751
+ model (type[BaseModel]): Pydantic model class.
752
+ depth (int): Indentation depth in the documentation.
753
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
754
+
755
+ Returns:
756
+ str: Generated text documentation for the field.
757
+ """
758
+ indent = " " * depth
759
+
760
+ field_info = model.model_fields.get(field_name)
761
+ field_description = field_info.description if field_info and field_info.description else ""
762
+
763
+ origin_type = get_origin(field_type)
764
+ origin_type = field_type if origin_type is None else origin_type
765
+
766
+ if origin_type == list:
767
+ element_type = get_args(field_type)[0]
768
+ field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)} of {format_model_and_field_name(element_type.__name__)})"
769
+ if field_description != "":
770
+ field_text += ":\n"
771
+ else:
772
+ field_text += "\n"
773
+ elif origin_type == Union:
774
+ element_types = get_args(field_type)
775
+ types = []
776
+ for element_type in element_types:
777
+ types.append(format_model_and_field_name(element_type.__name__))
778
+ field_text = f"{indent}{field_name} ({' or '.join(types)})"
779
+ if field_description != "":
780
+ field_text += ":\n"
781
+ else:
782
+ field_text += "\n"
783
+ else:
784
+ field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)})"
785
+ if field_description != "":
786
+ field_text += ":\n"
787
+ else:
788
+ field_text += "\n"
789
+
790
+ if not documentation_with_field_description:
791
+ return field_text
792
+
793
+ if field_description != "":
794
+ field_text += f" Description: {field_description}\n"
795
+
796
+ # Check for and include field-specific examples if available
797
+ if hasattr(model, "Config") and hasattr(model.Config,
798
+ "json_schema_extra") and "example" in model.Config.json_schema_extra:
799
+ field_example = model.Config.json_schema_extra["example"].get(field_name)
800
+ if field_example is not None:
801
+ example_text = f"'{field_example}'" if isinstance(field_example, str) else field_example
802
+ field_text += f"{indent} Example: {example_text}\n"
803
+
804
+ if isclass(origin_type) and issubclass(origin_type, BaseModel):
805
+ field_text += f"{indent} Details:\n"
806
+ for name, type_ in get_type_hints(field_type).items():
807
+ field_text += generate_field_markdown(name, type_, field_type, depth + 2)
808
+
809
+ return field_text
810
+
811
+
812
+ def format_json_example(example: dict[str, Any], depth: int) -> str:
813
+ """
814
+ Format a JSON example into a readable string with indentation.
815
+
816
+ Args:
817
+ example (dict): JSON example to be formatted.
818
+ depth (int): Indentation depth.
819
+
820
+ Returns:
821
+ str: Formatted JSON example string.
822
+ """
823
+ indent = " " * depth
824
+ formatted_example = "{\n"
825
+ for key, value in example.items():
826
+ value_text = f"'{value}'" if isinstance(value, str) else value
827
+ formatted_example += f"{indent}{key}: {value_text},\n"
828
+ formatted_example = formatted_example.rstrip(",\n") + "\n" + indent + "}"
829
+ return formatted_example
830
+
831
+
832
+ def generate_text_documentation(
833
+ pydantic_models: list[type[BaseModel]], model_prefix="Model", fields_prefix="Fields",
834
+ documentation_with_field_description=True
835
+ ) -> str:
836
+ """
837
+ Generate text documentation for a list of Pydantic models.
838
+
839
+ Args:
840
+ pydantic_models (list[type[BaseModel]]): List of Pydantic model classes.
841
+ model_prefix (str): Prefix for the model section.
842
+ fields_prefix (str): Prefix for the fields section.
843
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
844
+
845
+ Returns:
846
+ str: Generated text documentation.
847
+ """
848
+ documentation = ""
849
+ pyd_models: list[tuple[type[BaseModel], bool]] = [(model, True) for model in pydantic_models]
850
+ for model, add_prefix in pyd_models:
851
+ if add_prefix:
852
+ documentation += f"{model_prefix}: {model.__name__}\n"
853
+ else:
854
+ documentation += f"Model: {model.__name__}\n"
855
+
856
+ # Handling multi-line model description with proper indentation
857
+
858
+ class_doc = getdoc(model)
859
+ base_class_doc = getdoc(BaseModel)
860
+ class_description = class_doc if class_doc and class_doc != base_class_doc else ""
861
+ if class_description != "":
862
+ documentation += " Description: "
863
+ documentation += "\n" + format_multiline_description(class_description, 2) + "\n"
864
+
865
+ if isclass(model) and issubclass(model, BaseModel):
866
+ documentation_fields = ""
867
+ for name, field_type in get_type_hints(model).items():
868
+ # if name == "markdown_code_block":
869
+ # continue
870
+ if get_origin(field_type) == list:
871
+ element_type = get_args(field_type)[0]
872
+ if isclass(element_type) and issubclass(element_type, BaseModel):
873
+ pyd_models.append((element_type, False))
874
+ if get_origin(field_type) == Union:
875
+ element_types = get_args(field_type)
876
+ for element_type in element_types:
877
+ if isclass(element_type) and issubclass(element_type, BaseModel):
878
+ pyd_models.append((element_type, False))
879
+ documentation_fields += generate_field_text(
880
+ name, field_type, model, documentation_with_field_description=documentation_with_field_description
881
+ )
882
+ if documentation_fields != "":
883
+ if add_prefix:
884
+ documentation += f" {fields_prefix}:\n{documentation_fields}"
885
+ else:
886
+ documentation += f" Fields:\n{documentation_fields}"
887
+ documentation += "\n"
888
+
889
+ if hasattr(model, "Config") and hasattr(model.Config,
890
+ "json_schema_extra") and "example" in model.Config.json_schema_extra:
891
+ documentation += f" Expected Example Output for {format_model_and_field_name(model.__name__)}:\n"
892
+ json_example = json.dumps(model.Config.json_schema_extra["example"])
893
+ documentation += format_multiline_description(json_example, 2) + "\n"
894
+
895
+ return documentation
896
+
897
+
898
+ def generate_field_text(
899
+ field_name: str, field_type: type[Any], model: type[BaseModel], depth=1,
900
+ documentation_with_field_description=True
901
+ ) -> str:
902
+ """
903
+ Generate text documentation for a Pydantic model field.
904
+
905
+ Args:
906
+ field_name (str): Name of the field.
907
+ field_type (type[Any]): Type of the field.
908
+ model (type[BaseModel]): Pydantic model class.
909
+ depth (int): Indentation depth in the documentation.
910
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
911
+
912
+ Returns:
913
+ str: Generated text documentation for the field.
914
+ """
915
+ indent = " " * depth
916
+
917
+ field_info = model.model_fields.get(field_name)
918
+ field_description = field_info.description if field_info and field_info.description else ""
919
+
920
+ if get_origin(field_type) == list:
921
+ element_type = get_args(field_type)[0]
922
+ field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)} of {format_model_and_field_name(element_type.__name__)})"
923
+ if field_description != "":
924
+ field_text += ":\n"
925
+ else:
926
+ field_text += "\n"
927
+ elif get_origin(field_type) == Union:
928
+ element_types = get_args(field_type)
929
+ types = []
930
+ for element_type in element_types:
931
+ types.append(format_model_and_field_name(element_type.__name__))
932
+ field_text = f"{indent}{field_name} ({' or '.join(types)})"
933
+ if field_description != "":
934
+ field_text += ":\n"
935
+ else:
936
+ field_text += "\n"
937
+ else:
938
+ field_text = f"{indent}{field_name} ({format_model_and_field_name(field_type.__name__)})"
939
+ if field_description != "":
940
+ field_text += ":\n"
941
+ else:
942
+ field_text += "\n"
943
+
944
+ if not documentation_with_field_description:
945
+ return field_text
946
+
947
+ if field_description != "":
948
+ field_text += f"{indent} Description: " + field_description + "\n"
949
+
950
+ # Check for and include field-specific examples if available
951
+ if hasattr(model, "Config") and hasattr(model.Config,
952
+ "json_schema_extra") and "example" in model.Config.json_schema_extra:
953
+ field_example = model.Config.json_schema_extra["example"].get(field_name)
954
+ if field_example is not None:
955
+ example_text = f"'{field_example}'" if isinstance(field_example, str) else field_example
956
+ field_text += f"{indent} Example: {example_text}\n"
957
+
958
+ if isclass(field_type) and issubclass(field_type, BaseModel):
959
+ field_text += f"{indent} Details:\n"
960
+ for name, type_ in get_type_hints(field_type).items():
961
+ field_text += generate_field_text(name, type_, field_type, depth + 2)
962
+
963
+ return field_text
964
+
965
+
966
+ def format_multiline_description(description: str, indent_level: int) -> str:
967
+ """
968
+ Format a multiline description with proper indentation.
969
+
970
+ Args:
971
+ description (str): Multiline description.
972
+ indent_level (int): Indentation level.
973
+
974
+ Returns:
975
+ str: Formatted multiline description.
976
+ """
977
+ indent = " " * indent_level
978
+ return indent + description.replace("\n", "\n" + indent)
979
+
980
+
981
+ def save_gbnf_grammar_and_documentation(
982
+ grammar, documentation, grammar_file_path="./grammar.gbnf", documentation_file_path="./grammar_documentation.md"
983
+ ):
984
+ """
985
+ Save GBNF grammar and documentation to specified files.
986
+
987
+ Args:
988
+ grammar (str): GBNF grammar string.
989
+ documentation (str): Documentation string.
990
+ grammar_file_path (str): File path to save the GBNF grammar.
991
+ documentation_file_path (str): File path to save the documentation.
992
+
993
+ Returns:
994
+ None
995
+ """
996
+ try:
997
+ with open(grammar_file_path, "w") as file:
998
+ file.write(grammar + get_primitive_grammar(grammar))
999
+ print(f"Grammar successfully saved to {grammar_file_path}")
1000
+ except IOError as e:
1001
+ print(f"An error occurred while saving the grammar file: {e}")
1002
+
1003
+ try:
1004
+ with open(documentation_file_path, "w") as file:
1005
+ file.write(documentation)
1006
+ print(f"Documentation successfully saved to {documentation_file_path}")
1007
+ except IOError as e:
1008
+ print(f"An error occurred while saving the documentation file: {e}")
1009
+
1010
+
1011
+ def remove_empty_lines(string):
1012
+ """
1013
+ Remove empty lines from a string.
1014
+
1015
+ Args:
1016
+ string (str): Input string.
1017
+
1018
+ Returns:
1019
+ str: String with empty lines removed.
1020
+ """
1021
+ lines = string.splitlines()
1022
+ non_empty_lines = [line for line in lines if line.strip() != ""]
1023
+ string_no_empty_lines = "\n".join(non_empty_lines)
1024
+ return string_no_empty_lines
1025
+
1026
+
1027
+ def generate_and_save_gbnf_grammar_and_documentation(
1028
+ pydantic_model_list,
1029
+ grammar_file_path="./generated_grammar.gbnf",
1030
+ documentation_file_path="./generated_grammar_documentation.md",
1031
+ outer_object_name: str | None = None,
1032
+ outer_object_content: str | None = None,
1033
+ model_prefix: str = "Output Model",
1034
+ fields_prefix: str = "Output Fields",
1035
+ list_of_outputs: bool = False,
1036
+ documentation_with_field_description=True,
1037
+ ):
1038
+ """
1039
+ Generate GBNF grammar and documentation, and save them to specified files.
1040
+
1041
+ Args:
1042
+ pydantic_model_list: List of Pydantic model classes.
1043
+ grammar_file_path (str): File path to save the generated GBNF grammar.
1044
+ documentation_file_path (str): File path to save the generated documentation.
1045
+ outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
1046
+ outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
1047
+ model_prefix (str): Prefix for the model section in the documentation.
1048
+ fields_prefix (str): Prefix for the fields section in the documentation.
1049
+ list_of_outputs (bool): Whether the output is a list of items.
1050
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
1051
+
1052
+ Returns:
1053
+ None
1054
+ """
1055
+ documentation = generate_markdown_documentation(
1056
+ pydantic_model_list, model_prefix, fields_prefix,
1057
+ documentation_with_field_description=documentation_with_field_description
1058
+ )
1059
+ grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
1060
+ list_of_outputs)
1061
+ grammar = remove_empty_lines(grammar)
1062
+ save_gbnf_grammar_and_documentation(grammar, documentation, grammar_file_path, documentation_file_path)
1063
+
1064
+
1065
+ def generate_gbnf_grammar_and_documentation(
1066
+ pydantic_model_list,
1067
+ outer_object_name: str | None = None,
1068
+ outer_object_content: str | None = None,
1069
+ model_prefix: str = "Output Model",
1070
+ fields_prefix: str = "Output Fields",
1071
+ list_of_outputs: bool = False,
1072
+ documentation_with_field_description=True,
1073
+ ):
1074
+ """
1075
+ Generate GBNF grammar and documentation for a list of Pydantic models.
1076
+
1077
+ Args:
1078
+ pydantic_model_list: List of Pydantic model classes.
1079
+ outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
1080
+ outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
1081
+ model_prefix (str): Prefix for the model section in the documentation.
1082
+ fields_prefix (str): Prefix for the fields section in the documentation.
1083
+ list_of_outputs (bool): Whether the output is a list of items.
1084
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
1085
+
1086
+ Returns:
1087
+ tuple: GBNF grammar string, documentation string.
1088
+ """
1089
+ documentation = generate_markdown_documentation(
1090
+ copy(pydantic_model_list), model_prefix, fields_prefix,
1091
+ documentation_with_field_description=documentation_with_field_description
1092
+ )
1093
+ grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
1094
+ list_of_outputs)
1095
+ grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
1096
+ return grammar, documentation
1097
+
1098
+
1099
+ def generate_gbnf_grammar_and_documentation_from_dictionaries(
1100
+ dictionaries: list[dict[str, Any]],
1101
+ outer_object_name: str | None = None,
1102
+ outer_object_content: str | None = None,
1103
+ model_prefix: str = "Output Model",
1104
+ fields_prefix: str = "Output Fields",
1105
+ list_of_outputs: bool = False,
1106
+ documentation_with_field_description=True,
1107
+ ):
1108
+ """
1109
+ Generate GBNF grammar and documentation from a list of dictionaries.
1110
+
1111
+ Args:
1112
+ dictionaries (list[dict]): List of dictionaries representing Pydantic models.
1113
+ outer_object_name (str): Outer object name for the GBNF grammar. If None, no outer object will be generated. Eg. "function" for function calling.
1114
+ outer_object_content (str): Content for the outer rule in the GBNF grammar. Eg. "function_parameters" or "params" for function calling.
1115
+ model_prefix (str): Prefix for the model section in the documentation.
1116
+ fields_prefix (str): Prefix for the fields section in the documentation.
1117
+ list_of_outputs (bool): Whether the output is a list of items.
1118
+ documentation_with_field_description (bool): Include field descriptions in the documentation.
1119
+
1120
+ Returns:
1121
+ tuple: GBNF grammar string, documentation string.
1122
+ """
1123
+ pydantic_model_list = create_dynamic_models_from_dictionaries(dictionaries)
1124
+ documentation = generate_markdown_documentation(
1125
+ copy(pydantic_model_list), model_prefix, fields_prefix,
1126
+ documentation_with_field_description=documentation_with_field_description
1127
+ )
1128
+ grammar = generate_gbnf_grammar_from_pydantic_models(pydantic_model_list, outer_object_name, outer_object_content,
1129
+ list_of_outputs)
1130
+ grammar = remove_empty_lines(grammar + get_primitive_grammar(grammar))
1131
+ return grammar, documentation
1132
+
1133
+
1134
+ def create_dynamic_model_from_function(func: Callable[..., Any]):
1135
+ """
1136
+ Creates a dynamic Pydantic model from a given function's type hints and adds the function as a 'run' method.
1137
+
1138
+ Args:
1139
+ func (Callable): A function with type hints from which to create the model.
1140
+
1141
+ Returns:
1142
+ A dynamic Pydantic model class with the provided function as a 'run' method.
1143
+ """
1144
+
1145
+ # Get the signature of the function
1146
+ sig = inspect.signature(func)
1147
+
1148
+ # Parse the docstring
1149
+ assert func.__doc__ is not None
1150
+ docstring = parse(func.__doc__)
1151
+
1152
+ dynamic_fields = {}
1153
+ param_docs = []
1154
+ for param in sig.parameters.values():
1155
+ # Exclude 'self' parameter
1156
+ if param.name == "self":
1157
+ continue
1158
+
1159
+ # Assert that the parameter has a type annotation
1160
+ if param.annotation == inspect.Parameter.empty:
1161
+ raise TypeError(f"""Parameter '{param.name}' in function '{getattr(func, "__name__", "")}' lacks a type annotation""")
1162
+
1163
+ # Find the parameter's description in the docstring
1164
+ param_doc = next((d for d in docstring.params if d.arg_name == param.name), None)
1165
+
1166
+ # Assert that the parameter has a description
1167
+ if not param_doc or not param_doc.description:
1168
+ raise ValueError(
1169
+ f"""Parameter '{param.name}' in function '{getattr(func, "__name__", "")}' lacks a description in the docstring""")
1170
+
1171
+ # Add parameter details to the schema
1172
+ param_docs.append((param.name, param_doc))
1173
+ if param.default == inspect.Parameter.empty:
1174
+ default_value = ...
1175
+ else:
1176
+ default_value = param.default
1177
+ dynamic_fields[param.name] = (
1178
+ param.annotation if param.annotation != inspect.Parameter.empty else str, default_value)
1179
+ # Creating the dynamic model
1180
+ dynamic_model = create_model(f"{getattr(func, '__name__')}", **dynamic_fields)
1181
+
1182
+ for name, param_doc in param_docs:
1183
+ dynamic_model.model_fields[name].description = param_doc.description
1184
+
1185
+ dynamic_model.__doc__ = docstring.short_description
1186
+
1187
+ def run_method_wrapper(self):
1188
+ func_args = {name: getattr(self, name) for name, _ in dynamic_fields.items()}
1189
+ return func(**func_args)
1190
+
1191
+ # Adding the wrapped function as a 'run' method
1192
+ setattr(dynamic_model, "run", run_method_wrapper)
1193
+ return dynamic_model
1194
+
1195
+
1196
+ def add_run_method_to_dynamic_model(model: type[BaseModel], func: Callable[..., Any]):
1197
+ """
1198
+ Add a 'run' method to a dynamic Pydantic model, using the provided function.
1199
+
1200
+ Args:
1201
+ model (type[BaseModel]): Dynamic Pydantic model class.
1202
+ func (Callable): Function to be added as a 'run' method to the model.
1203
+
1204
+ Returns:
1205
+ type[BaseModel]: Pydantic model class with the added 'run' method.
1206
+ """
1207
+
1208
+ def run_method_wrapper(self):
1209
+ func_args = {name: getattr(self, name) for name in model.model_fields}
1210
+ return func(**func_args)
1211
+
1212
+ # Adding the wrapped function as a 'run' method
1213
+ setattr(model, "run", run_method_wrapper)
1214
+
1215
+ return model
1216
+
1217
+
1218
+ def create_dynamic_models_from_dictionaries(dictionaries: list[dict[str, Any]]):
1219
+ """
1220
+ Create a list of dynamic Pydantic model classes from a list of dictionaries.
1221
+
1222
+ Args:
1223
+ dictionaries (list[dict]): List of dictionaries representing model structures.
1224
+
1225
+ Returns:
1226
+ list[type[BaseModel]]: List of generated dynamic Pydantic model classes.
1227
+ """
1228
+ dynamic_models = []
1229
+ for func in dictionaries:
1230
+ model_name = format_model_and_field_name(func.get("name", ""))
1231
+ dyn_model = convert_dictionary_to_pydantic_model(func, model_name)
1232
+ dynamic_models.append(dyn_model)
1233
+ return dynamic_models
1234
+
1235
+
1236
+ def map_grammar_names_to_pydantic_model_class(pydantic_model_list):
1237
+ output = {}
1238
+ for model in pydantic_model_list:
1239
+ output[format_model_and_field_name(model.__name__)] = model
1240
+
1241
+ return output
1242
+
1243
+
1244
+ def json_schema_to_python_types(schema):
1245
+ type_map = {
1246
+ "any": Any,
1247
+ "string": str,
1248
+ "number": float,
1249
+ "integer": int,
1250
+ "boolean": bool,
1251
+ "array": list,
1252
+ }
1253
+ return type_map[schema]
1254
+
1255
+
1256
+ def list_to_enum(enum_name, values):
1257
+ return Enum(enum_name, {value: value for value in values})
1258
+
1259
+
1260
+ def convert_dictionary_to_pydantic_model(dictionary: dict[str, Any], model_name: str = "CustomModel") -> type[Any]:
1261
+ """
1262
+ Convert a dictionary to a Pydantic model class.
1263
+
1264
+ Args:
1265
+ dictionary (dict): Dictionary representing the model structure.
1266
+ model_name (str): Name of the generated Pydantic model.
1267
+
1268
+ Returns:
1269
+ type[BaseModel]: Generated Pydantic model class.
1270
+ """
1271
+ fields: dict[str, Any] = {}
1272
+
1273
+ if "properties" in dictionary:
1274
+ for field_name, field_data in dictionary.get("properties", {}).items():
1275
+ if field_data == "object":
1276
+ submodel = convert_dictionary_to_pydantic_model(dictionary, f"{model_name}_{field_name}")
1277
+ fields[field_name] = (submodel, ...)
1278
+ else:
1279
+ field_type = field_data.get("type", "str")
1280
+
1281
+ if field_data.get("enum", []):
1282
+ fields[field_name] = (list_to_enum(field_name, field_data.get("enum", [])), ...)
1283
+ elif field_type == "array":
1284
+ items = field_data.get("items", {})
1285
+ if items != {}:
1286
+ array = {"properties": items}
1287
+ array_type = convert_dictionary_to_pydantic_model(array, f"{model_name}_{field_name}_items")
1288
+ fields[field_name] = (list[array_type], ...) # ty: ignore[invalid-type-form]
1289
+ else:
1290
+ fields[field_name] = (list, ...)
1291
+ elif field_type == "object":
1292
+ submodel = convert_dictionary_to_pydantic_model(field_data, f"{model_name}_{field_name}")
1293
+ fields[field_name] = (submodel, ...)
1294
+ elif field_type == "required":
1295
+ required = field_data.get("enum", [])
1296
+ for key, field in fields.items():
1297
+ if key not in required:
1298
+ optional_type = fields[key][0]
1299
+ fields[key] = (Optional[optional_type], ...)
1300
+ else:
1301
+ field_type = json_schema_to_python_types(field_type)
1302
+ fields[field_name] = (field_type, ...)
1303
+ if "function" in dictionary:
1304
+ for field_name, field_data in dictionary.get("function", {}).items():
1305
+ if field_name == "name":
1306
+ model_name = field_data
1307
+ elif field_name == "description":
1308
+ fields["__doc__"] = field_data
1309
+ elif field_name == "parameters":
1310
+ return convert_dictionary_to_pydantic_model(field_data, f"{model_name}")
1311
+
1312
+ if "parameters" in dictionary:
1313
+ field_data = {"function": dictionary}
1314
+ return convert_dictionary_to_pydantic_model(field_data, f"{model_name}")
1315
+ if "required" in dictionary:
1316
+ required = dictionary.get("required", [])
1317
+ for key, field in fields.items():
1318
+ if key not in required:
1319
+ optional_type = fields[key][0]
1320
+ fields[key] = (Optional[optional_type], ...)
1321
+ custom_model = create_model(model_name, **fields)
1322
+ return custom_model
backend/llama.cpp/examples/pydantic_models_to_grammar_examples.py ADDED
@@ -0,0 +1,312 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ """Function calling example using pydantic models."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import datetime
9
+ import json
10
+ import logging
11
+ import textwrap
12
+ import sys
13
+ from enum import Enum
14
+ from typing import Optional, Union
15
+
16
+ import requests
17
+ from pydantic import BaseModel, Field
18
+ from pydantic_models_to_grammar import (add_run_method_to_dynamic_model, convert_dictionary_to_pydantic_model,
19
+ create_dynamic_model_from_function, generate_gbnf_grammar_and_documentation)
20
+
21
+
22
+ def create_completion(host, prompt, gbnf_grammar):
23
+ """Calls the /completion API on llama-server.
24
+
25
+ See
26
+ https://github.com/ggml-org/llama.cpp/tree/HEAD/tools/server#api-endpoints
27
+ """
28
+ print(f" Request:\n Grammar:\n{textwrap.indent(gbnf_grammar, ' ')}\n Prompt:\n{textwrap.indent(prompt.rstrip(), ' ')}")
29
+ headers = {"Content-Type": "application/json"}
30
+ data = {"prompt": prompt, "grammar": gbnf_grammar}
31
+ result = requests.post(f"http://{host}/completion", headers=headers, json=data).json()
32
+ assert data.get("error") is None, data
33
+ logging.info("Result: %s", result)
34
+ content = result["content"]
35
+ print(f" Model: {result['model']}")
36
+ print(f" Result:\n{textwrap.indent(json.dumps(json.loads(content), indent=2), ' ')}")
37
+ return content
38
+
39
+
40
+ # A function for the agent to send a message to the user.
41
+ class SendMessageToUser(BaseModel):
42
+ """Send a message to the User."""
43
+ chain_of_thought: str = Field(..., description="Your chain of thought while sending the message.")
44
+ message: str = Field(..., description="Message you want to send to the user.")
45
+
46
+ def run(self):
47
+ print(f"SendMessageToUser: {self.message}")
48
+
49
+
50
+ def example_rce(host):
51
+ """Minimal test case where the LLM call an arbitrary python function."""
52
+ print("- example_rce")
53
+ tools = [SendMessageToUser]
54
+ gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
55
+ pydantic_model_list=tools, outer_object_name="function",
56
+ outer_object_content="function_parameters", model_prefix="Function", fields_prefix="Parameters")
57
+ system_message = "You are an advanced AI, tasked to assist the user by calling functions in JSON format. The following are the available functions and their parameters and types:\n\n" + documentation
58
+ user_message = "What is 42 * 42?"
59
+ prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{user_message}<|im_end|>\n<|im_start|>assistant"
60
+ text = create_completion(host, prompt, gbnf_grammar)
61
+ json_data = json.loads(text)
62
+ tools_map = {tool.__name__:tool for tool in tools}
63
+ # This finds "SendMessageToUser":
64
+ tool = tools_map.get(json_data["function"])
65
+ if not tool:
66
+ print(f"Error: unknown tool {json_data['function']}")
67
+ return 1
68
+ tool(**json_data["function_parameters"]).run()
69
+ return 0
70
+
71
+
72
+ # Enum for the calculator tool.
73
+ class MathOperation(Enum):
74
+ ADD = "add"
75
+ SUBTRACT = "subtract"
76
+ MULTIPLY = "multiply"
77
+ DIVIDE = "divide"
78
+
79
+
80
+ # Simple pydantic calculator tool for the agent that can add, subtract,
81
+ # multiply, and divide. Docstring and description of fields will be used in
82
+ # system prompt.
83
+ class Calculator(BaseModel):
84
+ """Perform a math operation on two numbers."""
85
+ number_one: Union[int, float] = Field(..., description="First number.")
86
+ operation: MathOperation = Field(..., description="Math operation to perform.")
87
+ number_two: Union[int, float] = Field(..., description="Second number.")
88
+
89
+ def run(self):
90
+ if self.operation == MathOperation.ADD:
91
+ return self.number_one + self.number_two
92
+ elif self.operation == MathOperation.SUBTRACT:
93
+ return self.number_one - self.number_two
94
+ elif self.operation == MathOperation.MULTIPLY:
95
+ return self.number_one * self.number_two
96
+ elif self.operation == MathOperation.DIVIDE:
97
+ return self.number_one / self.number_two
98
+ else:
99
+ raise ValueError("Unknown operation.")
100
+
101
+
102
+ def example_calculator(host):
103
+ """Have the LLM ask to get a calculation done.
104
+
105
+ Here the grammar gets generated by passing the available function models to
106
+ generate_gbnf_grammar_and_documentation function. This also generates a
107
+ documentation usable by the LLM.
108
+
109
+ pydantic_model_list is the list of pydantic models outer_object_name is an
110
+ optional name for an outer object around the actual model object. Like a
111
+ "function" object with "function_parameters" which contains the actual model
112
+ object. If None, no outer object will be generated outer_object_content is
113
+ the name of outer object content.
114
+
115
+ model_prefix is the optional prefix for models in the documentation. (Default="Output Model")
116
+ fields_prefix is the prefix for the model fields in the documentation. (Default="Output Fields")
117
+ """
118
+ print("- example_calculator")
119
+ tools = [SendMessageToUser, Calculator]
120
+ gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
121
+ pydantic_model_list=tools, outer_object_name="function",
122
+ outer_object_content="function_parameters", model_prefix="Function", fields_prefix="Parameters")
123
+ system_message = "You are an advanced AI, tasked to assist the user by calling functions in JSON format. The following are the available functions and their parameters and types:\n\n" + documentation
124
+ user_message1 = "What is 42 * 42?"
125
+ prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{user_message1}<|im_end|>\n<|im_start|>assistant"
126
+ text = create_completion(host, prompt, gbnf_grammar)
127
+ json_data = json.loads(text)
128
+ expected = {
129
+ "function": "Calculator",
130
+ "function_parameters": {
131
+ "number_one": 42,
132
+ "operation": "multiply",
133
+ "number_two": 42
134
+ }
135
+ }
136
+ if json_data != expected:
137
+ print(" Result is not as expected!")
138
+ tools_map = {tool.__name__:tool for tool in tools}
139
+ # This finds "Calculator":
140
+ tool = tools_map.get(json_data["function"])
141
+ if not tool:
142
+ print(f"Error: unknown tool {json_data['function']}")
143
+ return 1
144
+ result = tool(**json_data["function_parameters"]).run()
145
+ print(f" Call {json_data['function']} gave result {result}")
146
+ return 0
147
+
148
+
149
+ class Category(Enum):
150
+ """The category of the book."""
151
+ Fiction = "Fiction"
152
+ NonFiction = "Non-Fiction"
153
+
154
+
155
+ class Book(BaseModel):
156
+ """Represents an entry about a book."""
157
+ title: str = Field(..., description="Title of the book.")
158
+ author: str = Field(..., description="Author of the book.")
159
+ published_year: Optional[int] = Field(..., description="Publishing year of the book.")
160
+ keywords: list[str] = Field(..., description="A list of keywords.")
161
+ category: Category = Field(..., description="Category of the book.")
162
+ summary: str = Field(..., description="Summary of the book.")
163
+
164
+
165
+ def example_struct(host):
166
+ """A example structured output based on pydantic models.
167
+
168
+ The LLM will create an entry for a Book database out of an unstructured
169
+ text. We need no additional parameters other than our list of pydantic
170
+ models.
171
+ """
172
+ print("- example_struct")
173
+ tools = [Book]
174
+ gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(pydantic_model_list=tools)
175
+ system_message = "You are an advanced AI, tasked to create a dataset entry in JSON for a Book. The following is the expected output model:\n\n" + documentation
176
+ text = """The Feynman Lectures on Physics is a physics textbook based on some lectures by Richard Feynman, a Nobel laureate who has sometimes been called "The Great Explainer". The lectures were presented before undergraduate students at the California Institute of Technology (Caltech), during 1961–1963. The book's co-authors are Feynman, Robert B. Leighton, and Matthew Sands."""
177
+ prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant"
178
+ text = create_completion(host, prompt, gbnf_grammar)
179
+ json_data = json.loads(text)
180
+ # In this case, there's no function nor function_parameters.
181
+ # Here the result will vary based on the LLM used.
182
+ keys = sorted(["title", "author", "published_year", "keywords", "category", "summary"])
183
+ if keys != sorted(json_data.keys()):
184
+ print(f"Unexpected result: {sorted(json_data.keys())}")
185
+ return 1
186
+ book = Book(**json_data)
187
+ print(f" As a Book object: %s" % book)
188
+ return 0
189
+
190
+
191
+ def get_current_datetime(output_format: Optional[str] = None):
192
+ """Get the current date and time in the given format.
193
+
194
+ Args:
195
+ output_format: formatting string for the date and time, defaults to '%Y-%m-%d %H:%M:%S'
196
+ """
197
+ return datetime.datetime.now().strftime(output_format or "%Y-%m-%d %H:%M:%S")
198
+
199
+
200
+ # Example function to get the weather.
201
+ def get_current_weather(location, unit):
202
+ """Get the current weather in a given location"""
203
+ if "London" in location:
204
+ return json.dumps({"location": "London", "temperature": "42", "unit": unit.value})
205
+ elif "New York" in location:
206
+ return json.dumps({"location": "New York", "temperature": "24", "unit": unit.value})
207
+ elif "North Pole" in location:
208
+ return json.dumps({"location": "North Pole", "temperature": "-42", "unit": unit.value})
209
+ return json.dumps({"location": location, "temperature": "unknown"})
210
+
211
+
212
+ def example_concurrent(host):
213
+ """An example for parallel function calling with a Python function, a pydantic
214
+ function model and an OpenAI like function definition.
215
+ """
216
+ print("- example_concurrent")
217
+ # Function definition in OpenAI style.
218
+ current_weather_tool = {
219
+ "type": "function",
220
+ "function": {
221
+ "name": "get_current_weather",
222
+ "description": "Get the current weather in a given location",
223
+ "parameters": {
224
+ "type": "object",
225
+ "properties": {
226
+ "location": {
227
+ "type": "string",
228
+ "description": "The city and state, e.g. San Francisco, CA",
229
+ },
230
+ "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
231
+ },
232
+ "required": ["location"],
233
+ },
234
+ },
235
+ }
236
+ # Convert OpenAI function definition into pydantic model.
237
+ current_weather_tool_model = convert_dictionary_to_pydantic_model(current_weather_tool)
238
+ # Add the actual function to a pydantic model.
239
+ current_weather_tool_model = add_run_method_to_dynamic_model(current_weather_tool_model, get_current_weather)
240
+
241
+ # Convert normal Python function to a pydantic model.
242
+ current_datetime_model = create_dynamic_model_from_function(get_current_datetime)
243
+
244
+ tools = [SendMessageToUser, Calculator, current_datetime_model, current_weather_tool_model]
245
+ gbnf_grammar, documentation = generate_gbnf_grammar_and_documentation(
246
+ pydantic_model_list=tools, outer_object_name="function",
247
+ outer_object_content="params", model_prefix="Function", fields_prefix="Parameters", list_of_outputs=True)
248
+ system_message = "You are an advanced AI assistant. You are interacting with the user and with your environment by calling functions. You call functions by writing JSON objects, which represent specific function calls.\nBelow is a list of your available function calls:\n\n" + documentation
249
+ text = """Get the date and time, get the current weather in celsius in London and solve the following calculation: 42 * 42"""
250
+ prompt = f"<|im_start|>system\n{system_message}<|im_end|>\n<|im_start|>user\n{text}<|im_end|>\n<|im_start|>assistant"
251
+ text = create_completion(host, prompt, gbnf_grammar)
252
+ json_data = json.loads(text)
253
+ expected = [
254
+ {
255
+ "function": "get_current_datetime",
256
+ "params": {
257
+ "output_format": "%Y-%m-%d %H:%M:%S"
258
+ }
259
+ },
260
+ {
261
+ "function": "get_current_weather",
262
+ "params": {
263
+ "location": "London",
264
+ "unit": "celsius"
265
+ }
266
+ },
267
+ {
268
+ "function": "Calculator",
269
+ "params": {
270
+ "number_one": 42,
271
+ "operation": "multiply",
272
+ "number_two": 42
273
+ }
274
+ }
275
+ ]
276
+ res = 0
277
+ if json_data != expected:
278
+ print(" Result is not as expected!")
279
+ print(" This can happen on highly quantized models")
280
+ res = 1
281
+ tools_map = {tool.__name__:tool for tool in tools}
282
+ for call in json_data:
283
+ tool = tools_map.get(call["function"])
284
+ if not tool:
285
+ print(f"Error: unknown tool {call['function']}")
286
+ return 1
287
+ result = tool(**call["params"]).run()
288
+ print(f" Call {call['function']} returned {result}")
289
+ # Should output something like this:
290
+ # Call get_current_datetime returned 2024-07-15 09:50:38
291
+ # Call get_current_weather returned {"location": "London", "temperature": "42", "unit": "celsius"}
292
+ # Call Calculator returned 1764
293
+ return res
294
+
295
+
296
+ def main():
297
+ parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__)
298
+ parser.add_argument("--host", default="localhost:8080", help="llama.cpp server")
299
+ parser.add_argument("-v", "--verbose", action="store_true", help="enables logging")
300
+ args = parser.parse_args()
301
+ logging.basicConfig(level=logging.INFO if args.verbose else logging.ERROR)
302
+ ret = 0
303
+ # Comment out below to only run the example you want.
304
+ ret = ret or example_rce(args.host)
305
+ ret = ret or example_calculator(args.host)
306
+ ret = ret or example_struct(args.host)
307
+ ret = ret or example_concurrent(args.host)
308
+ return ret
309
+
310
+
311
+ if __name__ == "__main__":
312
+ sys.exit(main())
backend/llama.cpp/examples/reason-act.sh ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ cd `dirname $0`
4
+ cd ..
5
+
6
+ # get -m model parameter otherwise defer to default
7
+ if [ "$1" == "-m" ]; then
8
+ MODEL="-m $2 "
9
+ fi
10
+
11
+ ./llama-cli $MODEL --color \
12
+ -f ./prompts/reason-act.txt \
13
+ -i --interactive-first \
14
+ --top_k 10000 --temp 0.2 --repeat_penalty 1 -t 7 -c 2048 \
15
+ -r "Question:" -r "Observation:" --in-prefix " " \
16
+ -n -1
backend/llama.cpp/examples/regex_to_grammar.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json, subprocess, sys, os
2
+
3
+ assert len(sys.argv) >= 2
4
+ [_, pattern, *rest] = sys.argv
5
+
6
+ print(subprocess.check_output(
7
+ [
8
+ "python",
9
+ os.path.join(
10
+ os.path.dirname(os.path.realpath(__file__)),
11
+ "json_schema_to_grammar.py"),
12
+ *rest,
13
+ "-",
14
+ "--raw-pattern",
15
+ ],
16
+ text=True,
17
+ input=json.dumps({
18
+ "type": "string",
19
+ "pattern": pattern,
20
+ }, indent=2)))
backend/llama.cpp/examples/retrieval/CMakeLists.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ set(TARGET llama-retrieval)
2
+ add_executable(${TARGET} retrieval.cpp)
3
+ install(TARGETS ${TARGET} RUNTIME)
4
+ target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT})
5
+ target_compile_features(${TARGET} PRIVATE cxx_std_17)
backend/llama.cpp/examples/retrieval/README.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # llama.cpp/examples/retrieval
2
+
3
+ Demonstration of simple retrieval technique based on cosine similarity
4
+
5
+ More info:
6
+ https://github.com/ggml-org/llama.cpp/pull/6193
7
+
8
+ ### How to use
9
+
10
+ `retieval.cpp` has parameters of its own:
11
+ - `--context-file`: file to be embedded - state this option multiple times to embed multiple files
12
+ - `--chunk-size`: minimum size of each text chunk to be embedded
13
+ - `--chunk-separator`: STRING to divide chunks by. newline by default
14
+
15
+ `retrieval` example can be tested as follows:
16
+
17
+ ```bash
18
+ llama-retrieval --model ./models/bge-base-en-v1.5-f16.gguf --top-k 3 --context-file README.md --context-file License --chunk-size 100 --chunk-separator .
19
+ ```
20
+
21
+ This chunks and embeds all given files and starts a loop requesting query inputs:
22
+
23
+ ```
24
+ Enter query:
25
+ ```
26
+
27
+ On each query input, top k chunks are shown along with file name, chunk position within file and original text:
28
+
29
+ ```
30
+ Enter query: describe the mit license
31
+ batch_decode: n_tokens = 6, n_seq = 1
32
+ Top 3 similar chunks:
33
+ filename: README.md
34
+ filepos: 119
35
+ similarity: 0.762334
36
+ textdata:
37
+ png)
38
+
39
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
40
+
41
+ [Roadmap](https://github.
42
+ --------------------
43
+ filename: License
44
+ filepos: 0
45
+ similarity: 0.725146
46
+ textdata:
47
+ MIT License
48
+
49
+ Copyright (c) 2023 Georgi Gerganov
50
+
51
+ Permission is hereby granted, free of charge, to any person obtaining a copy
52
+ of this software and associated documentation files (the "Software"), to deal
53
+ in the Software without restriction, including without limitation the rights
54
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
55
+ copies of the Software, and to permit persons to whom the Software is
56
+ furnished to do so, subject to the following conditions:
57
+
58
+ The above copyright notice and this permission notice shall be included in all
59
+ copies or substantial portions of the Software.
60
+ --------------------
61
+ filename: README.md
62
+ filepos: 9178
63
+ similarity: 0.621722
64
+ textdata:
65
+ com/cztomsik/ava) (MIT)
66
+ - [ptsochantaris/emeltal](https://github.com/ptsochantaris/emeltal)
67
+ - [pythops/tenere](https://github.
68
+ --------------------
69
+ ```
backend/llama.cpp/examples/retrieval/retrieval.cpp ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "arg.h"
2
+ #include "common.h"
3
+ #include "log.h"
4
+ #include "llama.h"
5
+
6
+ #include <algorithm>
7
+ #include <clocale>
8
+ #include <fstream>
9
+ #include <iostream> // TODO: remove me
10
+
11
+ static void print_usage(int, char ** argv) {
12
+ LOG("\nexample usage:\n");
13
+ LOG("\n %s --model ./models/bge-base-en-v1.5-f16.gguf --top-k 3 --context-file README.md --context-file License --chunk-size 100 --chunk-separator .\n", argv[0]);
14
+ LOG("\n");
15
+ }
16
+
17
+ struct chunk {
18
+ // filename
19
+ std::string filename;
20
+ // original file position
21
+ size_t filepos;
22
+ // original text data
23
+ std::string textdata;
24
+ // tokenized text data
25
+ std::vector<llama_token> tokens;
26
+ // embedding
27
+ std::vector<float> embedding;
28
+ };
29
+
30
+ // chunk file data to chunks of size >= chunk_size
31
+ // chunk_separator is the separator between chunks
32
+ static std::vector<chunk> chunk_file(const std::string & filename, int chunk_size, const std::string & chunk_separator) {
33
+ std::vector<chunk> chunks;
34
+ std::ifstream f(filename.c_str());
35
+
36
+ if (!f.is_open()) {
37
+ LOG_ERR("could not open file %s\n", filename.c_str());
38
+ return chunks;
39
+ }
40
+
41
+ chunk current_chunk;
42
+ char buffer[1024];
43
+ int64_t filepos = 0;
44
+ std::string current;
45
+ while (f.read(buffer, 1024)) {
46
+ current += std::string(buffer, f.gcount());
47
+ size_t pos;
48
+ while ((pos = current.find(chunk_separator)) != std::string::npos) {
49
+ current_chunk.textdata += current.substr(0, pos + chunk_separator.size());
50
+ if ((int) current_chunk.textdata.size() > chunk_size) {
51
+ // save chunk
52
+ current_chunk.filepos = filepos;
53
+ current_chunk.filename = filename;
54
+ chunks.push_back(current_chunk);
55
+ // update filepos
56
+ filepos += (int) current_chunk.textdata.size();
57
+ // reset current_chunk
58
+ current_chunk = chunk();
59
+ }
60
+ current = current.substr(pos + chunk_separator.size());
61
+ }
62
+
63
+ }
64
+ // add leftover data to last chunk
65
+ if (current_chunk.textdata.size() > 0) {
66
+ if (chunks.empty()) {
67
+ current_chunk.filepos = filepos;
68
+ current_chunk.filename = filename;
69
+ chunks.push_back(current_chunk);
70
+ } else {
71
+ chunks.back().textdata += current_chunk.textdata;
72
+ }
73
+ }
74
+ f.close();
75
+ return chunks;
76
+ }
77
+
78
+ static void batch_add_seq(llama_batch & batch, const std::vector<int32_t> & tokens, llama_seq_id seq_id) {
79
+ size_t n_tokens = tokens.size();
80
+ for (size_t i = 0; i < n_tokens; i++) {
81
+ common_batch_add(batch, tokens[i], i, { seq_id }, true);
82
+ }
83
+ }
84
+
85
+ static void batch_process(llama_context * ctx, llama_batch & batch, float * output, int n_seq, int n_embd) {
86
+ // clear previous kv_cache values (irrelevant for embeddings)
87
+ llama_memory_clear(llama_get_memory(ctx), false);
88
+
89
+ // run model
90
+ LOG_INF("%s: n_tokens = %d, n_seq = %d\n", __func__, batch.n_tokens, n_seq);
91
+ if (llama_decode(ctx, batch) < 0) {
92
+ LOG_ERR("%s : failed to process\n", __func__);
93
+ }
94
+
95
+ for (int i = 0; i < batch.n_tokens; i++) {
96
+ if (!batch.logits[i]) {
97
+ continue;
98
+ }
99
+
100
+ // try to get sequence embeddings - supported only when pooling_type is not NONE
101
+ const float * embd = llama_get_embeddings_seq(ctx, batch.seq_id[i][0]);
102
+ if (embd == NULL) {
103
+ embd = llama_get_embeddings_ith(ctx, i);
104
+ if (embd == NULL) {
105
+ LOG_ERR("%s: failed to get embeddings for token %d\n", __func__, i);
106
+ continue;
107
+ }
108
+ }
109
+
110
+ float * out = output + batch.seq_id[i][0] * n_embd;
111
+ common_embd_normalize(embd, out, n_embd, 2);
112
+ }
113
+ }
114
+
115
+ int main(int argc, char ** argv) {
116
+ std::setlocale(LC_NUMERIC, "C");
117
+
118
+ common_params params;
119
+
120
+ common_init();
121
+
122
+ if (!common_params_parse(argc, argv, params, LLAMA_EXAMPLE_RETRIEVAL, print_usage)) {
123
+ return 1;
124
+ }
125
+
126
+ // For BERT models, batch size must be equal to ubatch size
127
+ params.n_ubatch = params.n_batch;
128
+ params.embedding = true;
129
+
130
+ if (params.chunk_size <= 0) {
131
+ LOG_ERR("chunk_size must be positive\n");
132
+ return 1;
133
+ }
134
+ if (params.context_files.empty()) {
135
+ LOG_ERR("context_files must be specified\n");
136
+ return 1;
137
+ }
138
+
139
+ LOG_INF("processing files:\n");
140
+ for (auto & context_file : params.context_files) {
141
+ LOG_INF("%s\n", context_file.c_str());
142
+ }
143
+
144
+ std::vector<chunk> chunks;
145
+ for (auto & context_file : params.context_files) {
146
+ std::vector<chunk> file_chunk = chunk_file(context_file, params.chunk_size, params.chunk_separator);
147
+ chunks.insert(chunks.end(), file_chunk.begin(), file_chunk.end());
148
+ }
149
+ LOG_INF("Number of chunks: %zu\n", chunks.size());
150
+
151
+ llama_backend_init();
152
+ llama_numa_init(params.numa);
153
+
154
+ // load the model
155
+ auto llama_init = common_init_from_params(params);
156
+
157
+ auto * model = llama_init->model();
158
+ auto * ctx = llama_init->context();
159
+
160
+ if (model == NULL) {
161
+ LOG_ERR("%s: unable to load model\n", __func__);
162
+ return 1;
163
+ }
164
+
165
+ const llama_vocab * vocab = llama_model_get_vocab(model);
166
+
167
+ const int n_ctx_train = llama_model_n_ctx_train(model);
168
+ const int n_ctx = llama_n_ctx(ctx);
169
+
170
+ const enum llama_pooling_type pooling_type = llama_pooling_type(ctx);
171
+ if (pooling_type == LLAMA_POOLING_TYPE_NONE) {
172
+ LOG_ERR("%s: pooling type NONE not supported\n", __func__);
173
+ return 1;
174
+ }
175
+
176
+ if (n_ctx > n_ctx_train) {
177
+ LOG_WRN("%s: warning: model was trained on only %d context tokens (%d specified)\n",
178
+ __func__, n_ctx_train, n_ctx);
179
+ }
180
+
181
+ // print system information
182
+ {
183
+ LOG_INF("\n");
184
+ LOG_INF("%s\n", common_params_get_system_info(params).c_str());
185
+ }
186
+
187
+ // max batch size
188
+ const uint64_t n_batch = params.n_batch;
189
+ GGML_ASSERT(params.n_batch >= params.n_ctx);
190
+
191
+ // tokenize the prompts and trim
192
+ for (auto & chunk : chunks) {
193
+ auto inp = common_tokenize(ctx, chunk.textdata, true, false);
194
+ if (inp.size() > n_batch) {
195
+ LOG_ERR("%s: chunk size (%lld) exceeds batch size (%lld), increase batch size and re-run\n",
196
+ __func__, (long long int) inp.size(), (long long int) n_batch);
197
+ return 1;
198
+ }
199
+ // add eos if not present
200
+ if (llama_vocab_eos(vocab) >= 0 && (inp.empty() || inp.back() != llama_vocab_eos(vocab))) {
201
+ inp.push_back(llama_vocab_eos(vocab));
202
+ }
203
+ chunk.tokens = inp;
204
+ }
205
+
206
+ // tokenization stats
207
+ if (params.verbose_prompt) {
208
+ for (int i = 0; i < (int) chunks.size(); i++) {
209
+ LOG_INF("%s: prompt %d: '%s'\n", __func__, i, chunks[i].textdata.c_str());
210
+ LOG_INF("%s: number of tokens in prompt = %zu\n", __func__, chunks[i].tokens.size());
211
+ for (int j = 0; j < (int) chunks[i].tokens.size(); j++) {
212
+ LOG_INF("%6d -> '%s'\n", chunks[i].tokens[j], common_token_to_piece(ctx, chunks[i].tokens[j]).c_str());
213
+ }
214
+ LOG_INF("\n\n");
215
+ }
216
+ }
217
+
218
+ // initialize batch
219
+ const int n_chunks = chunks.size();
220
+ struct llama_batch batch = llama_batch_init(n_batch, 0, 1);
221
+
222
+ // allocate output
223
+ const int n_embd_out = llama_model_n_embd_out(model);
224
+ std::vector<float> embeddings(n_chunks * n_embd_out, 0);
225
+ float * emb = embeddings.data();
226
+
227
+ // break into batches
228
+ unsigned int p = 0; // number of prompts processed already
229
+ unsigned int s = 0; // number of prompts in current batch
230
+ for (int k = 0; k < n_chunks; k++) {
231
+ // clamp to n_batch tokens
232
+ auto & inp = chunks[k].tokens;
233
+
234
+ const uint64_t n_toks = inp.size();
235
+
236
+ // encode if at capacity
237
+ if (batch.n_tokens + n_toks > n_batch || s >= llama_n_seq_max(ctx)) {
238
+ float * out = emb + p * n_embd_out;
239
+ batch_process(ctx, batch, out, s, n_embd_out);
240
+ common_batch_clear(batch);
241
+ p += s;
242
+ s = 0;
243
+ }
244
+
245
+ // add to batch
246
+ batch_add_seq(batch, inp, s);
247
+ s += 1;
248
+ }
249
+
250
+ // final batch
251
+ float * out = emb + p * n_embd_out;
252
+ batch_process(ctx, batch, out, s, n_embd_out);
253
+
254
+ // save embeddings to chunks
255
+ for (int i = 0; i < n_chunks; i++) {
256
+ chunks[i].embedding = std::vector<float>(emb + i * n_embd_out, emb + (i + 1) * n_embd_out);
257
+ // clear tokens as they are no longer needed
258
+ chunks[i].tokens.clear();
259
+ }
260
+
261
+ struct llama_batch query_batch = llama_batch_init(n_batch, 0, 1);
262
+
263
+ // start loop, receive query and return top k similar chunks based on cosine similarity
264
+ std::string query;
265
+ while (true) {
266
+ LOG("Enter query: ");
267
+ std::getline(std::cin, query);
268
+ std::vector<int32_t> query_tokens = common_tokenize(ctx, query, true);
269
+
270
+ batch_add_seq(query_batch, query_tokens, 0);
271
+
272
+ std::vector<float> query_emb(n_embd_out, 0);
273
+ batch_process(ctx, query_batch, query_emb.data(), 1, n_embd_out);
274
+
275
+ common_batch_clear(query_batch);
276
+
277
+ // compute cosine similarities
278
+ {
279
+ std::vector<std::pair<int, float>> similarities;
280
+ for (int i = 0; i < n_chunks; i++) {
281
+ float sim = common_embd_similarity_cos(chunks[i].embedding.data(), query_emb.data(), n_embd_out);
282
+ similarities.push_back(std::make_pair(i, sim));
283
+ }
284
+
285
+ // sort similarities
286
+ std::sort(similarities.begin(), similarities.end(), [](const std::pair<int, float> & a, const std::pair<int, float> & b) {
287
+ return a.second > b.second;
288
+ });
289
+
290
+ LOG("Top %d similar chunks:\n", params.sampling.top_k);
291
+ for (int i = 0; i < std::min(params.sampling.top_k, (int) chunks.size()); i++) {
292
+ LOG("filename: %s\n", chunks[similarities[i].first].filename.c_str());
293
+ LOG("filepos: %lld\n", (long long int) chunks[similarities[i].first].filepos);
294
+ LOG("similarity: %f\n", similarities[i].second);
295
+ LOG("textdata:\n%s\n", chunks[similarities[i].first].textdata.c_str());
296
+ LOG("--------------------\n");
297
+ }
298
+ }
299
+ }
300
+
301
+ LOG("\n");
302
+ llama_perf_context_print(ctx);
303
+
304
+ // clean up
305
+ llama_batch_free(query_batch);
306
+ llama_backend_free();
307
+ }
backend/llama.cpp/examples/server-llama2-13B.sh ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env bash
2
+
3
+ set -e
4
+
5
+ cd "$(dirname "$0")/.." || exit
6
+
7
+ # Specify the model you want to use here:
8
+ MODEL="${MODEL:-./models/llama-2-13b-chat.ggmlv3.q5_K_M.bin}"
9
+ PROMPT_TEMPLATE=${PROMPT_TEMPLATE:-./prompts/chat-system.txt}
10
+
11
+ # Adjust to the number of CPU cores you want to use.
12
+ N_THREAD="${N_THREAD:-12}"
13
+
14
+ # Note: you can also override the generation options by specifying them on the command line:
15
+ GEN_OPTIONS="${GEN_OPTIONS:---ctx_size 4096 --batch-size 1024}"
16
+
17
+
18
+ # shellcheck disable=SC2086 # Intended splitting of GEN_OPTIONS
19
+ ./llama-server $GEN_OPTIONS \
20
+ --model "$MODEL" \
21
+ --threads "$N_THREAD" \
22
+ --rope-freq-scale 1.0 \
23
+ "$@"
24
+
25
+ # I used this to test the model with mps, but omitted it from the general purpose. If you want to use it, just specify it on the command line.
26
+ # -ngl 1 \
backend/llama.cpp/examples/server_embd.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import asyncio.threads
3
+ import requests
4
+ import numpy as np
5
+
6
+
7
+ n = 8
8
+
9
+ result = []
10
+
11
+ async def requests_post_async(*args, **kwargs):
12
+ return await asyncio.threads.to_thread(requests.post, *args, **kwargs)
13
+
14
+ async def main():
15
+ model_url = "http://127.0.0.1:6900"
16
+ responses: list[requests.Response] = await asyncio.gather(*[requests_post_async(
17
+ url= f"{model_url}/embedding",
18
+ json= {"content": "a "*1022}
19
+ ) for i in range(n)])
20
+
21
+ for response in responses:
22
+ embedding = response.json()["embedding"]
23
+ print(embedding[-8:])
24
+ result.append(embedding)
25
+
26
+ asyncio.run(main())
27
+
28
+ # compute cosine similarity
29
+
30
+ for i in range(n-1):
31
+ for j in range(i+1, n):
32
+ embedding1 = np.array(result[i])
33
+ embedding2 = np.array(result[j])
34
+ similarity = np.dot(embedding1, embedding2) / (np.linalg.norm(embedding1) * np.linalg.norm(embedding2))
35
+ print(f"Similarity between {i} and {j}: {similarity:.2f}")
backend/llama.cpp/examples/simple-chat/CMakeLists.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ set(TARGET llama-simple-chat)
2
+ add_executable(${TARGET} simple-chat.cpp)
3
+ install(TARGETS ${TARGET} RUNTIME)
4
+ target_link_libraries(${TARGET} PRIVATE llama ${CMAKE_THREAD_LIBS_INIT})
5
+ target_compile_features(${TARGET} PRIVATE cxx_std_17)
backend/llama.cpp/examples/simple-chat/README.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # llama.cpp/example/simple-chat
2
+
3
+ The purpose of this example is to demonstrate a minimal usage of llama.cpp to create a simple chat program using the chat template from the GGUF file.
4
+
5
+ ```bash
6
+ ./llama-simple-chat -m Meta-Llama-3.1-8B-Instruct.gguf -c 2048
7
+ ...
backend/llama.cpp/examples/simple-chat/simple-chat.cpp ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "llama.h"
2
+ #include <clocale>
3
+ #include <cstdio>
4
+ #include <cstring>
5
+ #include <iostream>
6
+ #include <string>
7
+ #include <vector>
8
+
9
+ static void print_usage(int, char ** argv) {
10
+ printf("\nexample usage:\n");
11
+ printf("\n %s -m model.gguf [-c context_size] [-ngl n_gpu_layers]\n", argv[0]);
12
+ printf("\n");
13
+ }
14
+
15
+ int main(int argc, char ** argv) {
16
+ std::setlocale(LC_NUMERIC, "C");
17
+
18
+ std::string model_path;
19
+ int ngl = 99;
20
+ int n_ctx = 2048;
21
+
22
+ // parse command line arguments
23
+ for (int i = 1; i < argc; i++) {
24
+ try {
25
+ if (strcmp(argv[i], "-m") == 0) {
26
+ if (i + 1 < argc) {
27
+ model_path = argv[++i];
28
+ } else {
29
+ print_usage(argc, argv);
30
+ return 1;
31
+ }
32
+ } else if (strcmp(argv[i], "-c") == 0) {
33
+ if (i + 1 < argc) {
34
+ n_ctx = std::stoi(argv[++i]);
35
+ } else {
36
+ print_usage(argc, argv);
37
+ return 1;
38
+ }
39
+ } else if (strcmp(argv[i], "-ngl") == 0) {
40
+ if (i + 1 < argc) {
41
+ ngl = std::stoi(argv[++i]);
42
+ } else {
43
+ print_usage(argc, argv);
44
+ return 1;
45
+ }
46
+ } else {
47
+ print_usage(argc, argv);
48
+ return 1;
49
+ }
50
+ } catch (std::exception & e) {
51
+ fprintf(stderr, "error: %s\n", e.what());
52
+ print_usage(argc, argv);
53
+ return 1;
54
+ }
55
+ }
56
+ if (model_path.empty()) {
57
+ print_usage(argc, argv);
58
+ return 1;
59
+ }
60
+
61
+ // only print errors
62
+ llama_log_set([](enum ggml_log_level level, const char * text, void * /* user_data */) {
63
+ if (level >= GGML_LOG_LEVEL_ERROR) {
64
+ fprintf(stderr, "%s", text);
65
+ }
66
+ }, nullptr);
67
+
68
+ // load dynamic backends
69
+ ggml_backend_load_all();
70
+
71
+ // initialize the model
72
+ llama_model_params model_params = llama_model_default_params();
73
+ model_params.n_gpu_layers = ngl;
74
+
75
+ llama_model * model = llama_model_load_from_file(model_path.c_str(), model_params);
76
+ if (!model) {
77
+ fprintf(stderr , "%s: error: unable to load model\n" , __func__);
78
+ return 1;
79
+ }
80
+
81
+ const llama_vocab * vocab = llama_model_get_vocab(model);
82
+
83
+ // initialize the context
84
+ llama_context_params ctx_params = llama_context_default_params();
85
+ ctx_params.n_ctx = n_ctx;
86
+ ctx_params.n_batch = n_ctx;
87
+
88
+ llama_context * ctx = llama_init_from_model(model, ctx_params);
89
+ if (!ctx) {
90
+ fprintf(stderr , "%s: error: failed to create the llama_context\n" , __func__);
91
+ return 1;
92
+ }
93
+
94
+ // initialize the sampler
95
+ llama_sampler * smpl = llama_sampler_chain_init(llama_sampler_chain_default_params());
96
+ llama_sampler_chain_add(smpl, llama_sampler_init_min_p(0.05f, 1));
97
+ llama_sampler_chain_add(smpl, llama_sampler_init_temp(0.8f));
98
+ llama_sampler_chain_add(smpl, llama_sampler_init_dist(LLAMA_DEFAULT_SEED));
99
+
100
+ // helper function to evaluate a prompt and generate a response
101
+ auto generate = [&](const std::string & prompt) {
102
+ std::string response;
103
+
104
+ const bool is_first = llama_memory_seq_pos_max(llama_get_memory(ctx), 0) == -1;
105
+
106
+ // tokenize the prompt
107
+ const int n_prompt_tokens = -llama_tokenize(vocab, prompt.c_str(), prompt.size(), NULL, 0, is_first, true);
108
+ std::vector<llama_token> prompt_tokens(n_prompt_tokens);
109
+ if (llama_tokenize(vocab, prompt.c_str(), prompt.size(), prompt_tokens.data(), prompt_tokens.size(), is_first, true) < 0) {
110
+ GGML_ABORT("failed to tokenize the prompt\n");
111
+ }
112
+
113
+ // prepare a batch for the prompt
114
+ llama_batch batch = llama_batch_get_one(prompt_tokens.data(), prompt_tokens.size());
115
+ llama_token new_token_id;
116
+ while (true) {
117
+ // check if we have enough space in the context to evaluate this batch
118
+ int n_ctx = llama_n_ctx(ctx);
119
+ int n_ctx_used = llama_memory_seq_pos_max(llama_get_memory(ctx), 0) + 1;
120
+ if (n_ctx_used + batch.n_tokens > n_ctx) {
121
+ printf("\033[0m\n");
122
+ fprintf(stderr, "context size exceeded\n");
123
+ exit(0);
124
+ }
125
+
126
+ int ret = llama_decode(ctx, batch);
127
+ if (ret != 0) {
128
+ GGML_ABORT("failed to decode, ret = %d\n", ret);
129
+ }
130
+
131
+ // sample the next token
132
+ new_token_id = llama_sampler_sample(smpl, ctx, -1);
133
+
134
+ // is it an end of generation?
135
+ if (llama_vocab_is_eog(vocab, new_token_id)) {
136
+ break;
137
+ }
138
+
139
+ // convert the token to a string, print it and add it to the response
140
+ char buf[256];
141
+ int n = llama_token_to_piece(vocab, new_token_id, buf, sizeof(buf), 0, true);
142
+ if (n < 0) {
143
+ GGML_ABORT("failed to convert token to piece\n");
144
+ }
145
+ std::string piece(buf, n);
146
+ printf("%s", piece.c_str());
147
+ fflush(stdout);
148
+ response += piece;
149
+
150
+ // prepare the next batch with the sampled token
151
+ batch = llama_batch_get_one(&new_token_id, 1);
152
+ }
153
+
154
+ return response;
155
+ };
156
+
157
+ std::vector<llama_chat_message> messages;
158
+ std::vector<char> formatted(llama_n_ctx(ctx));
159
+ int prev_len = 0;
160
+ while (true) {
161
+ // get user input
162
+ printf("\033[32m> \033[0m");
163
+ std::string user;
164
+ std::getline(std::cin, user);
165
+
166
+ if (user.empty()) {
167
+ break;
168
+ }
169
+
170
+ const char * tmpl = llama_model_chat_template(model, /* name */ nullptr);
171
+
172
+ // add the user input to the message list and format it
173
+ messages.push_back({"user", strdup(user.c_str())});
174
+ int new_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, formatted.data(), formatted.size());
175
+ if (new_len > (int)formatted.size()) {
176
+ formatted.resize(new_len);
177
+ new_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), true, formatted.data(), formatted.size());
178
+ }
179
+ if (new_len < 0) {
180
+ fprintf(stderr, "failed to apply the chat template\n");
181
+ return 1;
182
+ }
183
+
184
+ // remove previous messages to obtain the prompt to generate the response
185
+ std::string prompt(formatted.begin() + prev_len, formatted.begin() + new_len);
186
+
187
+ // generate a response
188
+ printf("\033[33m");
189
+ std::string response = generate(prompt);
190
+ printf("\n\033[0m");
191
+
192
+ // add the response to the messages
193
+ messages.push_back({"assistant", strdup(response.c_str())});
194
+ prev_len = llama_chat_apply_template(tmpl, messages.data(), messages.size(), false, nullptr, 0);
195
+ if (prev_len < 0) {
196
+ fprintf(stderr, "failed to apply the chat template\n");
197
+ return 1;
198
+ }
199
+ }
200
+
201
+ // free resources
202
+ for (auto & msg : messages) {
203
+ free(const_cast<char *>(msg.content));
204
+ }
205
+ llama_sampler_free(smpl);
206
+ llama_free(ctx);
207
+ llama_model_free(model);
208
+
209
+ return 0;
210
+ }
backend/llama.cpp/examples/simple-cmake-pkg/.gitignore ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Prerequisites
2
+ *.d
3
+
4
+ # Compiled Object files
5
+ *.slo
6
+ *.lo
7
+ *.o
8
+ *.obj
9
+
10
+ # Precompiled Headers
11
+ *.gch
12
+ *.pch
13
+
14
+ # Compiled Dynamic libraries
15
+ *.so
16
+ *.dylib
17
+ *.dll
18
+
19
+ # Fortran module files
20
+ *.mod
21
+ *.smod
22
+
23
+ # Compiled Static libraries
24
+ *.lai
25
+ *.la
26
+ *.a
27
+ *.lib
28
+
29
+ # Executables
30
+ *.exe
31
+ *.out
32
+ *.app
33
+
34
+ *.gguf
35
+
36
+ *.log
37
+ .DS_Store
38
+ .build/
39
+ .cache/
40
+ .direnv/
41
+ .envrc
42
+ .swiftpm
43
+ .venv
44
+ .clang-tidy
45
+ .vs/
46
+ .vscode/
47
+
48
+ build*/
49
+ out/
50
+ tmp/