gguf-dump.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env python3
  2. from __future__ import annotations
  3. import logging
  4. import argparse
  5. import os
  6. import sys
  7. from pathlib import Path
  8. from typing import Any
  9. import numpy as np
  10. # Necessary to load the local gguf package
  11. if "NO_LOCAL_GGUF" not in os.environ and (Path(__file__).parent.parent.parent / 'gguf-py').exists():
  12. sys.path.insert(0, str(Path(__file__).parent.parent))
  13. from gguf import GGUFReader, GGUFValueType # noqa: E402
  14. logger = logging.getLogger("gguf-dump")
  15. def get_file_host_endian(reader: GGUFReader) -> tuple[str, str]:
  16. host_endian = 'LITTLE' if np.uint32(1) == np.uint32(1).newbyteorder("<") else 'BIG'
  17. if reader.byte_order == 'S':
  18. file_endian = 'BIG' if host_endian == 'LITTLE' else 'LITTLE'
  19. else:
  20. file_endian = host_endian
  21. return (host_endian, file_endian)
  22. # For more information about what field.parts and field.data represent,
  23. # please see the comments in the modify_gguf.py example.
  24. def dump_metadata(reader: GGUFReader, args: argparse.Namespace) -> None:
  25. host_endian, file_endian = get_file_host_endian(reader)
  26. print(f'* File is {file_endian} endian, script is running on a {host_endian} endian host.') # noqa: NP100
  27. print(f'* Dumping {len(reader.fields)} key/value pair(s)') # noqa: NP100
  28. for n, field in enumerate(reader.fields.values(), 1):
  29. if not field.types:
  30. pretty_type = 'N/A'
  31. elif field.types[0] == GGUFValueType.ARRAY:
  32. nest_count = len(field.types) - 1
  33. pretty_type = '[' * nest_count + str(field.types[-1].name) + ']' * nest_count
  34. else:
  35. pretty_type = str(field.types[-1].name)
  36. log_message = f' {n:5}: {pretty_type:10} | {len(field.data):8} | {field.name}'
  37. if len(field.types) == 1:
  38. curr_type = field.types[0]
  39. if curr_type == GGUFValueType.STRING:
  40. log_message += ' = {0}'.format(repr(str(bytes(field.parts[-1]), encoding='utf8')[:60]))
  41. elif field.types[0] in reader.gguf_scalar_to_np:
  42. log_message += ' = {0}'.format(field.parts[-1][0])
  43. print(log_message) # noqa: NP100
  44. if args.no_tensors:
  45. return
  46. print(f'* Dumping {len(reader.tensors)} tensor(s)') # noqa: NP100
  47. for n, tensor in enumerate(reader.tensors, 1):
  48. prettydims = ', '.join('{0:5}'.format(d) for d in list(tensor.shape) + [1] * (4 - len(tensor.shape)))
  49. print(f' {n:5}: {tensor.n_elements:10} | {prettydims} | {tensor.tensor_type.name:7} | {tensor.name}') # noqa: NP100
  50. def dump_metadata_json(reader: GGUFReader, args: argparse.Namespace) -> None:
  51. import json
  52. host_endian, file_endian = get_file_host_endian(reader)
  53. metadata: dict[str, Any] = {}
  54. tensors: dict[str, Any] = {}
  55. result = {
  56. "filename": args.model,
  57. "endian": file_endian,
  58. "metadata": metadata,
  59. "tensors": tensors,
  60. }
  61. for idx, field in enumerate(reader.fields.values()):
  62. curr: dict[str, Any] = {
  63. "index": idx,
  64. "type": field.types[0].name if field.types else 'UNKNOWN',
  65. "offset": field.offset,
  66. }
  67. metadata[field.name] = curr
  68. if field.types[:1] == [GGUFValueType.ARRAY]:
  69. curr["array_types"] = [t.name for t in field.types][1:]
  70. if not args.json_array:
  71. continue
  72. itype = field.types[-1]
  73. if itype == GGUFValueType.STRING:
  74. curr["value"] = [str(bytes(field.parts[idx]), encoding="utf-8") for idx in field.data]
  75. else:
  76. curr["value"] = [pv for idx in field.data for pv in field.parts[idx].tolist()]
  77. elif field.types[0] == GGUFValueType.STRING:
  78. curr["value"] = str(bytes(field.parts[-1]), encoding="utf-8")
  79. else:
  80. curr["value"] = field.parts[-1].tolist()[0]
  81. if not args.no_tensors:
  82. for idx, tensor in enumerate(reader.tensors):
  83. tensors[tensor.name] = {
  84. "index": idx,
  85. "shape": tensor.shape.tolist(),
  86. "type": tensor.tensor_type.name,
  87. "offset": tensor.field.offset,
  88. }
  89. json.dump(result, sys.stdout)
  90. def main() -> None:
  91. parser = argparse.ArgumentParser(description="Dump GGUF file metadata")
  92. parser.add_argument("model", type=str, help="GGUF format model filename")
  93. parser.add_argument("--no-tensors", action="store_true", help="Don't dump tensor metadata")
  94. parser.add_argument("--json", action="store_true", help="Produce JSON output")
  95. parser.add_argument("--json-array", action="store_true", help="Include full array values in JSON output (long)")
  96. parser.add_argument("--verbose", action="store_true", help="increase output verbosity")
  97. args = parser.parse_args(None if len(sys.argv) > 1 else ["--help"])
  98. logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
  99. if not args.json:
  100. logger.info(f'* Loading: {args.model}')
  101. reader = GGUFReader(args.model, 'r')
  102. if args.json:
  103. dump_metadata_json(reader, args)
  104. else:
  105. dump_metadata(reader, args)
  106. if __name__ == '__main__':
  107. main()