merge_lora_into_ggml.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. # Merges a LoRA checkpoint in PyTorch format (.pth) into an rwkv.cpp model file.
  2. # Usage: python merge_lora_into_ggml.py C:\rwkv.cpp-169M.bin C:\my-lora.pth 32 C:\rwkv.cpp-169M-with-my-lora.bin
  3. # LoRA format is compatible with https://github.com/Blealtan/RWKV-LM-LoRA
  4. # You need to know lora_alpha value to perform the merge.
  5. # Source model must be in either FP16 or FP32 format. Quantization can be performed after merging.
  6. import argparse
  7. import struct
  8. import torch
  9. import numpy as np
  10. from typing import List, Dict, Tuple
  11. def parse_args():
  12. parser = argparse.ArgumentParser(description='Merge a PyTorch LoRA checkpoint (.pth) into an rwkv.cpp model file')
  13. parser.add_argument('src_path', help='Path to source rwkv.cpp model')
  14. parser.add_argument('lora_path', help='Path to LoRA checkpoint in PyTorch format')
  15. parser.add_argument('lora_alpha', type=int, help='Value of lora_alpha parameter used when training this LoRA checkpoint')
  16. parser.add_argument('dest_path', help='Path to destination rwkv.cpp model, will be overwitten with the merged model')
  17. return parser.parse_args()
  18. def write_parameter(out_file, key: str, parameter: torch.Tensor) -> None:
  19. assert parameter.dtype == torch.float32 or parameter.dtype == torch.float16
  20. key_encoded: bytes = key.encode('utf-8')
  21. out_file.write(struct.pack(
  22. '=iii',
  23. len(parameter.shape),
  24. len(key_encoded),
  25. 1 if parameter.dtype == torch.float16 else 0
  26. ))
  27. # Dimension order is reversed here:
  28. # * PyTorch shape is (x rows, y columns)
  29. # * ggml shape is (y elements in a row, x elements in a column)
  30. # Both shapes represent the same tensor.
  31. for dim in reversed(parameter.shape):
  32. out_file.write(struct.pack('=i', dim))
  33. out_file.write(key_encoded)
  34. parameter.numpy().tofile(out_file)
  35. def main() -> None:
  36. args = parse_args()
  37. print(f'Reading {args.lora_path}')
  38. lora_state_dict: Dict[str, torch.Tensor] = torch.load(args.lora_path, map_location='cpu')
  39. print(f'Merging')
  40. with open(args.src_path, 'rb') as in_file, open(args.dest_path, 'wb') as out_file:
  41. # noinspection PyTypeChecker
  42. header: Tuple[int, int, int, int, int, int] = struct.unpack('=iiiiii', in_file.read(6 * 4))
  43. assert header[0] == 0x67676d66, 'Invalid magic value'
  44. assert 100 <= header[1] <= 101, 'Invalid version number'
  45. assert header[5] == 0 or header[5] == 1, 'Only FP32 and FP16 models are supported'
  46. out_file.write(struct.pack('=iiiiii', *header))
  47. while True:
  48. parameter_header_bytes: bytes = in_file.read(3 * 4)
  49. if len(parameter_header_bytes) == 0:
  50. break
  51. dim_count, key_length, data_type = struct.unpack('=iii', parameter_header_bytes)
  52. # noinspection PyTypeChecker
  53. shape: Tuple[int] = struct.unpack('=' + 'i' * dim_count, in_file.read(dim_count * 4))
  54. # ggml order to PyTorch
  55. shape: List[int] = [d for d in reversed(shape)]
  56. key: str = in_file.read(key_length).decode('utf-8')
  57. print(f'* {key} {shape}')
  58. assert data_type == 0 or data_type == 1, 'Only FP32 and FP16 models are supported'
  59. element_count: int = 1
  60. for dim in shape:
  61. element_count *= dim
  62. parameter_np: np.ndarray = np.frombuffer(
  63. in_file.read((2 if data_type == 1 else 4) * element_count),
  64. dtype=(np.half if data_type == 1 else np.single)
  65. )
  66. parameter: torch.Tensor = torch.tensor(parameter_np).view(shape)
  67. if key in lora_state_dict:
  68. replacement: torch.Tensor = lora_state_dict[key].float()
  69. # Same processing as in convert_pytorch_to_ggml.py
  70. if '.time_' in key:
  71. # (1, 1, n_embed) -> (n_embed)
  72. replacement = replacement.squeeze()
  73. if '.time_decay' in key:
  74. replacement = -torch.exp(replacement)
  75. if parameter.dtype == torch.float16:
  76. replacement = replacement.half()
  77. assert replacement.shape == parameter.shape, f'Parameter {key} has shape {parameter.shape} in model file ' \
  78. f'and shape {replacement.shape} in LoRA file'
  79. parameter = replacement
  80. print(f'Replaced parameter {key}')
  81. del lora_state_dict[key]
  82. for suffix in ['.weight', '']:
  83. lora_A_key: str = key.replace('.weight', '') + '.lora_A' + suffix
  84. lora_B_key: str = key.replace('.weight', '') + '.lora_B' + suffix
  85. if lora_A_key in lora_state_dict:
  86. lora_A: torch.Tensor = lora_state_dict[lora_A_key]
  87. lora_B: torch.Tensor = lora_state_dict[lora_B_key]
  88. assert lora_B.shape[1] == lora_A.shape[0], f'Invalid shape of LoRA matrices for {key}: ' \
  89. f'{lora_A.shape}, {lora_B.shape}'
  90. lora_R: int = lora_B.shape[1]
  91. replacement: torch.Tensor = parameter + lora_B @ lora_A * (args.lora_alpha / lora_R)
  92. if parameter.dtype == torch.float16:
  93. replacement = replacement.half()
  94. parameter = replacement
  95. print(f'Merged LoRA into parameter {key}, lora_r = {lora_R}')
  96. del lora_state_dict[lora_A_key]
  97. del lora_state_dict[lora_B_key]
  98. break
  99. write_parameter(out_file, key, parameter)
  100. for key in lora_state_dict:
  101. print(f'WARNING: Unused parameter in LoRA state dict {key}')
  102. print('Done')
  103. if __name__ == "__main__":
  104. main()
粤ICP备19079148号