# 原版的internlm3-8b-instruct 转为paddle版本## 转换代码如下```pythonimport osimport argparseimport jsonimport shutilfrom typing import Dict, List, Tupleimport numpy as npimport paddleimport torchfrom safetensors.torch import load_file# 单个分片文件的最大大小 (字节) - 2GBMAX_SHARD_SIZE = 2 * 1024 * 1024 * 1024# 需要转置的权重名称后缀TRANSPOSE_SUFFIXES = [ "self_attn.k_proj.weight", "self_attn.v_proj.weight", "mlp.gate_proj.weight", "mlp.up_proj.weight", "mlp.down_proj.weight",]def should_transpose(name: str) -> bool: """判断权重是否需要转置""" for suffix in TRANSPOSE_SUFFIXES: if suffix in name: return True return Falsedef get_tensor_size(tensor: paddle.Tensor) -> int: """获取 tensor 占用的字节数""" # 数据类型到字节数的映射 dtype_bytes = { paddle.float32: 4, paddle.float16: 2, paddle.bfloat16: 2, paddle.int64: 8, paddle.int32: 4, paddle.int16: 2, paddle.int8: 1, paddle.uint8: 1, paddle.bool: 1, } dtype = tensor.dtype if dtype in dtype_bytes: return int(tensor.numel() * dtype_bytes[dtype]) else: # 尝试通过 numpy 获取 try: dtype_str = str(dtype).replace("paddle.", "") return int(tensor.numel() * np.dtype(dtype_str).itemsize) except: # 默认返回 float32 的大小 return int(tensor.numel() * 4)def convert_weight_to_paddle(tensor: torch.Tensor, transpose: bool = False) -> paddle.Tensor: """ 将 torch tensor 转换为 paddle tensor """ # bfloat16 需要先转换为 float32 再转 numpy original_dtype = tensor.dtype if tensor.dtype == torch.bfloat16: tensor = tensor.float() # 获取 numpy 数组 np_array = tensor.cpu().numpy() # 如果需要转置 if transpose: np_array = np_array.T.copy() # 转换为 paddle tensor paddle_tensor = paddle.to_tensor(np_array) # 如果原始是 bfloat16,转换回去 if original_dtype == torch.bfloat16: paddle_tensor = paddle_tensor.astype(paddle.bfloat16) return paddle_tensordef get_weight_dtype_str(tensor: torch.Tensor) -> str: """获取权重的数据类型字符串""" dtype_map = { torch.float32: "float32", torch.float16: "float16", torch.bfloat16: "bfloat16", torch.int64: "int64", torch.int32: "int32", } return dtype_map.get(tensor.dtype, str(tensor.dtype))def split_weights_to_shards( weights: Dict[str, paddle.Tensor], max_shard_size: int = MAX_SHARD_SIZE) -> List[Tuple[Dict[str, paddle.Tensor], int]]: """ 将权重分片,每个分片不超过 max_shard_size Returns: List of (shard_weights, shard_size) tuples """ shards = [] current_shard = {} current_size = 0 # 按名称排序,确保顺序一致 sorted_names = sorted(weights.keys()) for name in sorted_names: tensor = weights[name] tensor_size = get_tensor_size(tensor) # 如果单个 tensor 就超过限制,单独放在一个分片 if tensor_size > max_shard_size: if current_shard: shards.append((current_shard, current_size)) current_shard = {} current_size = 0 shards.append(({name: tensor}, tensor_size)) print(f" 警告: {name} 单独占用 {tensor_size / (1024**3):.2f} GB") continue # 如果加入当前分片会超限,先保存当前分片 if current_size + tensor_size > max_shard_size: if current_shard: shards.append((current_shard, current_size)) current_shard = {name: tensor} current_size = tensor_size else: current_shard[name] = tensor current_size += tensor_size # 保存最后一个分片 if current_shard: shards.append((current_shard, current_size)) return shardsdef convert_model( input_dir: str, output_dir: str, device: str = "cpu", max_shard_size: int = MAX_SHARD_SIZE,): """ 转换模型权重,支持分片保存 """ print("=" * 80) print("InternLM3 权重转换: Transformers -> Paddle (分片模式)") print("=" * 80) print(f"输入目录: {input_dir}") print(f"输出目录: {output_dir}") print(f"设备: {device}") print(f"单文件最大: {max_shard_size / (1024**3):.2f} GB") print() # 创建输出目录 os.makedirs(output_dir, exist_ok=True) # 1. 加载权重索引文件 index_file = os.path.join(input_dir, "model.safetensors.index.json") if os.path.exists(index_file): print("[1] 加载权重索引文件...") with open(index_file, "r") as f: index_data = json.load(f) weight_map = index_data["weight_map"] weight_files = sorted(set(weight_map.values())) print(f" 找到 {len(weight_files)} 个权重文件") else: print("[1] 检查权重文件...") weight_files = ["model.safetensors"] if os.path.exists(os.path.join(input_dir, "model.safetensors")) else [] if not weight_files: weight_files = [f for f in os.listdir(input_dir) if f.endswith(".bin") or f.endswith(".pt")] weight_map = {} # 2. 转换权重 print("\n[2] 转换权重...") converted_weights = {} total_params = 0 for weight_file in weight_files: print(f"\n 处理文件: {weight_file}") file_path = os.path.join(input_dir, weight_file) # 加载权重 if weight_file.endswith(".safetensors"): weights = load_file(file_path, device=device) else: weights = torch.load(file_path, map_location=device) for name, tensor in weights.items(): # 判断是否需要转置 transpose = should_transpose(name) # 转换为 paddle tensor paddle_tensor = convert_weight_to_paddle(tensor, transpose=transpose) converted_weights[name] = paddle_tensor total_params += tensor.numel() # 打印进度 dtype_str = get_weight_dtype_str(tensor) transpose_str = " [转置]" if transpose else "" print(f" {name}: {list(tensor.shape)} ({dtype_str}){transpose_str}") print(f"\n 总参数数量: {total_params:,}") # 3. 分片保存 print("\n[3] 分片保存...") shards = split_weights_to_shards(converted_weights, max_shard_size) # 创建权重索引 weight_index = {} total_size = 0 for i, (shard_weights, shard_size) in enumerate(shards): shard_name = f"model_state-{i+1:05d}-of-{len(shards):05d}.pdparams" shard_path = os.path.join(output_dir, shard_name) print(f"\n 保存分片 {i+1}/{len(shards)}: {shard_name}") print(f" 权重数量: {len(shard_weights)}") print(f" 文件大小: {shard_size / (1024**3):.2f} GB") # 保存分片 paddle.save(shard_weights, shard_path) # 更新索引 for name in shard_weights.keys(): weight_index[name] = shard_name total_size += shard_size # 保存权重索引文件 index_data = { "metadata": { "total_size": total_size, "total_params": total_params, }, "weight_map": weight_index, } index_path = os.path.join(output_dir, "model_state.pdparams.index.json") with open(index_path, "w") as f: json.dump(index_data, f, indent=2) print(f"\n 保存索引: {index_path}") print(f"\n 总文件大小: {total_size / (1024**3):.2f} GB") print(f" 分片数量: {len(shards)}") # 4. 复制配置文件 print("\n[4] 复制配置文件...") config_files = [ "config.json", "tokenizer_config.json", "special_tokens_map.json", "tokenizer.model", "generation_config.json", ] for config_file in config_files: src = os.path.join(input_dir, config_file) dst = os.path.join(output_dir, config_file) if os.path.exists(src): shutil.copy2(src, dst) print(f" 复制: {config_file}") print("\n" + "=" * 80) print("转换完成!") print("=" * 80) print(f"输出目录: {output_dir}")def main(): parser = argparse.ArgumentParser(description="InternLM3 权重转换脚本 (Transformers -> Paddle, 分片模式)") parser.add_argument( "--input_dir", type=str, default="/mnt/caoyuanye/llm/internlm/internlm3-8b-instruct", help="transformers 模型目录", ) parser.add_argument( "--output_dir", type=str, default=None, help="paddle 模型输出目录 (默认为 input_dir + '_paddle')", ) parser.add_argument( "--device", type=str, default="cpu", choices=["cpu", "cuda"], help="转换时使用的设备 (推荐 cpu 避免 OOM)", ) parser.add_argument( "--max_shard_size", type=int, default=MAX_SHARD_SIZE, help=f"单个分片文件最大大小 (字节,默认 2GB)", ) args = parser.parse_args() # 设置输出目录 if args.output_dir is None: args.output_dir = args.input_dir + "_paddle" # 执行转换 convert_model( input_dir=args.input_dir, output_dir=args.output_dir, device=args.device, max_shard_size=args.max_shard_size, )if __name__ == "__main__": main()```