android分配内存失败,Error:在OOM之前分配一个23970828字节、2097152个空闲字节和2MB的内存失败...

本文介绍了一种在Android应用中处理Out of Memory (OOM) 错误的方法,特别是在加载大型位图图像时出现的问题。文章详细展示了如何通过调整图像大小来避免内存溢出,并提供了具体的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Android:java.lang.OutOfMemoryError:在OOM之前分配一个23970828字节、2097152个空闲字节和2MB的内存失败

我想展示位图图像图像视图已经存储的SD卡。运行之后,我的应用程序就崩溃了OutOfMemyError错误:(java.lang.OutOfMemoryError:在OOM之前分配2097152个空闲字节和2MB的23970828字节分配失败)

我不知道,也不知道为什么会失去记忆。我认为我的图像大小非常大,所以我试图改变它。Iterator it = imageArray.iterator();while (it.hasNext()) {

Object element = it.next();

String objElement = element.toString();

Log.e("objElement ", " = " + objElement);

final ImageView imageView = new ImageView (getContext());

final ProgressBar pBar = new ProgressBar(getContext(), null,

android.R.attr.progressBarStyleSmall);

imageView.setTag(it);

pBar.setTag(it);

imageView.setImageResource(R.drawable.img_placeholder);

pBar.setVisibility(View.VISIBLE);

if (objElement.endsWith(mp3_Pattern)) {

Log.e("Mp3 ", " ends with ");

pBar.setVisibility(View.GONE);

imageView.setImageResource(R.drawable.audio_control);

}

if (objElement.endsWith(png_Pattern)) {

Bitmap bitmap = BitmapFactory.decodeFile(objElement);

int size = Math.min(bitmap.getWidth(), bitmap.getHeight());

int x = (bitmap.getWidth() - size) / 2;

int y = (bitmap.getHeight() - size) / 2;

Bitmap bitmap_Resul = Bitmap.createBitmap(bitmap, x, y, size, size);

Log.e("bitmap_Resul "," = "+ bitmap_Resul);

if (bitmap_Resul != bitmap) {

bitmap.recycle();

}

imageView.setImageBitmap(bitmap_Resul);

Log.e("png_Pattern ", " ends with ");

Log.e(" bitmap "," = " + bitmap);

}

holder.linearLayout.addView(imageView);

holder.linearLayout.addView(pBar);

日志猫信息:08-27 14:11:15.307    1857-1857/? E/AndroidRuntime﹕ FATAL EXCEPTION: main    Process: com.example.tazeen.classnkk, PID: 1857

java.lang.OutOfMemoryError: Failed to allocate a 23970828 byte allocation with 2097152 free bytes and 2MB until OOM

import os import torch import transformers from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, DataCollatorForLanguageModeling, BitsAndBytesConfig, Trainer ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from datasets import load_dataset import logging import psutil import gc from datetime import datetime # === 配置区域 === MODEL_NAME = "/home/vipuser/ai_writer_project_final_with_fixed_output_ui/models/Yi-6B" DATASET_PATH = "./data/train_lora_formatted.jsonl" OUTPUT_DIR = "./yi6b-lora-optimized" DEVICE_MAP = "auto" # 使用自动设备映射 # 确保输出目录存在 os.makedirs(OUTPUT_DIR, exist_ok=True) # === 增强的日志系统 === def setup_logging(output_dir): """配置日志系统,支持文件TensorBoard""" # 主日志配置 logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # 文件日志处理器 file_handler = logging.FileHandler(os.path.join(output_dir, "training.log")) file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) logger.addHandler(file_handler) # 控制台日志处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) logger.addHandler(console_handler) # TensorBoard日志目录 tensorboard_log_dir = os.path.join(output_dir, "logs", datetime.now().strftime("%Y%m%d-%H%M%S")) os.makedirs(tensorboard_log_dir, exist_ok=True) # 安装TensorBoard回调 tb_writer = None try: from torch.utils.tensorboard import SummaryWriter tb_writer = SummaryWriter(log_dir=tensorboard_log_dir) logger.info(f"TensorBoard日志目录: {tensorboard_log_dir}") except ImportError: logger.warning("TensorBoard未安装,可视化功能不可用") return logger, tb_writer logger, tb_writer = setup_logging(OUTPUT_DIR) # === 量化配置 - 使用更高效的配置 === quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) # === 加载模型 === logger.info("加载预训练模型...") model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, device_map=DEVICE_MAP, quantization_config=quant_config, torch_dtype=torch.bfloat16, trust_remote_code=True ) # === 分词器处理 === logger.info("加载分词器...") tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) tokenizer.padding_side = "right" if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token tokenizer.pad_token_id = tokenizer.eos_token_id # === 准备模型训练 === model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=False) # 关闭梯度检查点以加速训练 # === LoRA 配置 - 增加参数以提升模型能力 === logger.info("配置LoRA...") lora_config = LoraConfig( r=128, # 增加rank以提升模型能力 lora_alpha=64, # 增加alpha值 target_modules=["q_proj", "v_proj", "k_proj", "o_proj"], # 增加更多目标模块 lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) # 记录可训练参数 trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) total_params = sum(p.numel() for p in model.parameters()) logger.info(f"可训练参数: {trainable_params:,} / 总参数: {total_params:,} ({trainable_params / total_params:.2%})") # === 加载并预处理数据集 === logger.info("加载预处理数据集...") dataset = load_dataset("json", data_files=DATASET_PATH, split="train") # 文本过滤函数 def is_valid_text(example): text = example.get("text", "") return text is not None and len(text.strip()) > 200 # 增加最小长度要求 dataset = dataset.filter(is_valid_text) logger.info(f"过滤后数据集大小: {len(dataset)} 条") # 分词函数 - 使用更大的上下文窗口 def tokenize_function(examples): tokenized = tokenizer( examples["text"], padding="max_length", truncation=True, max_length=2048, # 增加上下文长度 ) # 创建 labels - 因果语言建模需要 labels = input_ids tokenized["labels"] = tokenized["input_ids"].copy() return tokenized tokenized_dataset = dataset.map( tokenize_function, batched=True, remove_columns=["text"], batch_size=128, # 增加批处理大小以加速处理 num_proc=8, # 使用更多进程 ) # === 数据整理器 === data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=False # 因果语言建模 ) # === 训练参数 - 优化以利用A100 80GB显存 === report_to_list = ["tensorboard"] if tb_writer else [] training_args = TrainingArguments( output_dir=OUTPUT_DIR, per_device_train_batch_size=16, # 大幅增加批次大小 gradient_accumulation_steps=1, # 减少梯度累积步数 learning_rate=2e-5, # 提高学习率 num_train_epochs=3, # 增加训练轮数 logging_steps=50, save_strategy="steps", save_steps=500, # 每500步保存一次 bf16=True, optim="paged_adamw_32bit", # 使用更稳定的优化器 report_to=report_to_list, # 如果TensorBoard可用则报告 warmup_ratio=0.05, # 增加预热比例 gradient_checkpointing=False, # 关闭梯度检查点以加速训练 fp16=False, max_grad_norm=1.0, # 增加梯度裁剪阈值 remove_unused_columns=False, dataloader_num_workers=8, # 增加数据加载工作线程 evaluation_strategy="steps", # 添加验证步骤 eval_steps=500, # 每500步验证一次 save_total_limit=3, # 保存更多检查点 logging_dir=os.path.join(OUTPUT_DIR, "logs"), load_best_model_at_end=True, # 训练结束时加载最佳模型 ddp_find_unused_parameters=False, logging_first_step=True, group_by_length=True, # 按长度分组以加速训练 lr_scheduler_type="cosine", # 使用余弦学习率调度器 weight_decay=0.01, # 添加权重衰减 ) # === GPU监控工具 === def monitor_gpu(): """监控GPU使用情况""" if torch.cuda.is_available(): device = torch.device("cuda") mem_alloc = torch.cuda.memory_allocated(device) / 1024 ** 3 mem_reserved = torch.cuda.memory_reserved(device) / 1024 ** 3 mem_total = torch.cuda.get_device_properties(device).total_memory / 1024 ** 3 return { "allocated": f"{mem_alloc:.2f} GB", "reserved": f"{mem_reserved:.2f} GB", "total": f"{mem_total:.2f} GB", "utilization": f"{mem_alloc / mem_total * 100:.1f}%" } return {} # === 创建训练器 === eval_dataset = None if len(tokenized_dataset) > 100: eval_dataset = tokenized_dataset.select(range(100)) trainer = Trainer( model=model, tokenizer=tokenizer, args=training_args, train_dataset=tokenized_dataset, eval_dataset=eval_dataset, data_collator=data_collator, ) # === 训练前验证 === def validate_data_and_model(): """验证数据模型是否准备好训练""" logger.info("\n=== 训练前验证 ===") # 检查样本格式 sample = tokenized_dataset[0] logger.info(f"样本键: {list(sample.keys())}") logger.info(f"input_ids 长度: {len(sample['input_ids'])}") logger.info(f"labels 长度: {len(sample['labels'])}") # 创建测试批次 test_batch = data_collator([sample, tokenized_dataset[1]]) # 移动数据到设备 test_batch = {k: v.to(model.device) for k, v in test_batch.items()} # 前向传播测试 model.train() outputs = model(**test_batch) loss_value = outputs.loss.item() logger.info(f"测试批次损失: {loss_value:.4f}") # 记录到TensorBoard if tb_writer: tb_writer.add_scalar("debug/test_loss", loss_value, 0) # 反向传播测试 outputs.loss.backward() logger.info("反向传播成功!") # 重置梯度 model.zero_grad() logger.info("验证完成,准备开始训练\n") # 记录初始GPU使用情况 gpu_status = monitor_gpu() logger.info(f"初始GPU状态: {gpu_status}") # 记录到TensorBoard if tb_writer: tb_writer.add_text("system/initial_gpu", str(gpu_status), 0) validate_data_and_model() # === 自定义回调 - 监控资源使用 === class ResourceMonitorCallback(transformers.TrainerCallback): def __init__(self, tb_writer=None): self.tb_writer = tb_writer self.start_time = datetime.now() def on_step_end(self, args, state, control, **kwargs): if state.global_step % 50 == 0: # GPU监控 gpu_status = monitor_gpu() logger.info(f"Step {state.global_step} - GPU状态: {gpu_status}") # CPU内存监控 cpu_percent = psutil.cpu_percent() mem = psutil.virtual_memory() logger.info( f"CPU使用率: {cpu_percent}%, 内存使用: {mem.used / 1024 ** 3:.2f}GB/{mem.total / 1024 ** 3:.2f}GB") # 记录到TensorBoard if self.tb_writer: # GPU显存使用 if torch.cuda.is_available(): device = torch.device("cuda") mem_alloc = torch.cuda.memory_allocated(device) / 1024 ** 3 self.tb_writer.add_scalar("system/gpu_mem", mem_alloc, state.global_step) # CPU使用率 self.tb_writer.add_scalar("system/cpu_usage", cpu_percent, state.global_step) # 系统内存使用 self.tb_writer.add_scalar("system/ram_usage", mem.used / 1024 ** 3, state.global_step) def on_log(self, args, state, control, logs=None, **kwargs): """记录训练指标到TensorBoard""" if self.tb_writer and logs is not None: for metric_name, metric_value in logs.items(): if "loss" in metric_name or "lr" in metric_name or "grad_norm" in metric_name: self.tb_writer.add_scalar(f"train/{metric_name}", metric_value, state.global_step) def on_train_end(self, args, state, control, **kwargs): """训练结束时记录总时间""" training_time = datetime.now() - self.start_time logger.info(f"训练总时间: {training_time}") if self.tb_writer: self.tb_writer.add_text("system/total_time", str(training_time)) # 添加回调 trainer.add_callback(ResourceMonitorCallback(tb_writer=tb_writer)) # === 启动训练 === try: logger.info("开始训练...") train_result = trainer.train() # 保存训练指标 metrics = train_result.metrics trainer.log_metrics("train", metrics) trainer.save_metrics("train", metrics) # 保存最佳模型 trainer.save_model(OUTPUT_DIR) tokenizer.save_pretrained(OUTPUT_DIR) logger.info(f"训练完成! 模型保存在: {OUTPUT_DIR}") # 记录最终指标到TensorBoard if tb_writer: for metric_name, metric_value in metrics.items(): tb_writer.add_scalar(f"final/{metric_name}", metric_value) tb_writer.close() except Exception as e: logger.error(f"训练出错: {e}") import traceback logger.error(traceback.format_exc()) # 尝试小批量训练 logger.info("\n尝试小批量训练...") small_dataset = tokenized_dataset.select(range(100)) trainer.train_dataset = small_dataset trainer.train() # 保存模型 trainer.save_model(f"{OUTPUT_DIR}_small") tokenizer.save_pretrained(f"{OUTPUT_DIR}_small") logger.info(f"小批量训练完成! 模型保存在: {OUTPUT_DIR}_small") # 记录错误到TensorBoard if tb_writer: tb_writer.add_text("error/exception", traceback.format_exc()) # 清理内存 del model del trainer gc.collect() torch.cuda.empty_cache() # === 训练后验证 === def validate_final_model(): """验证训练后的模型""" logger.info("\n=== 训练后验证 ===") # 加载保存的模型 from peft import PeftModel # 仅加载基础模型配置 base_model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, device_map=DEVICE_MAP, quantization_config=quant_config, torch_dtype=torch.bfloat16, trust_remote_code=True, load_in_4bit=True ) # 加载LoRA适配器 peft_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR) # 合并LoRA权重 merged_model = peft_model.merge_and_unload() # 测试生成 prompt = "中国的首都是" inputs = tokenizer(prompt, return_tensors="pt").to(merged_model.device) outputs = merged_model.generate( **inputs, max_new_tokens=100, # 生成长度增加 temperature=0.7, top_p=0.9, # 添加top-p采样 repetition_penalty=1.2, # 添加重复惩罚 do_sample=True ) generated = tokenizer.decode(outputs[0], skip_special_tokens=True) logger.info(f"提示: {prompt}") logger.info(f"生成结果: {generated}") # 记录到TensorBoard if tb_writer: tb_writer.add_text("validation/sample", f"提示: {prompt}\n生成: {generated}") # 更全面的测试 test_prompts = [ "人工智能的未来发展趋势是", "如何学习深度学习?", "写一个关于太空探索的短故事:" ] for i, test_prompt in enumerate(test_prompts): inputs = tokenizer(test_prompt, return_tensors="pt").to(merged_model.device) outputs = merged_model.generate( **inputs, max_new_tokens=150, temperature=0.7, top_p=0.9, repetition_penalty=1.2, do_sample=True ) generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True) logger.info(f"\n提示: {test_prompt}\n生成: {generated_text}\n{'=' * 50}") # 记录到TensorBoard if tb_writer: tb_writer.add_text(f"validation/test_{i}", f"提示: {test_prompt}\n生成: {generated_text}") logger.info("验证完成") # 执行验证 validate_final_model() # 关闭TensorBoard写入器 if tb_writer: tb_writer.close() logger.info("TensorBoard日志已关闭") torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 688.00 MiB. GPU 0 has a total capacity of 79.15 GiB of which 652.12 MiB is free. Including non-PyTorch memory, this process has 78.49 GiB memory in use. Of the allocated memory 76.57 GiB is allocated by PyTorch, and 1.42 GiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation. See documentation for Memory Management (https://pytorch.org/docs/stable/notes/cuda.html#environment-variables) 0%| | 0/21 [00:00<?, ?it/s]
最新发布
07-14
<think>我们遇到了一个CUDA内存不足的错误。错误信息显示我们试图分配688 MiB,但GPU上只有652.12 MiB空闲。同时,已经分配了76.57 GiB,还有1.42 GiB是预留给PyTorch但未分配的。 考虑到我们使用的是4位量化的模型(Yi-6B),理论上模型本身占用的显存应该大大减少(大约在6B*0.5=3GB左右,加上一些开销)。但是,训练过程中需要存储梯度、优化器状态激活值,这些都会增加显存使用。 我们当前的训练参数: per_device_train_batch_size=16 max_length=2048 每个样本的显存占用大约是:2048 tokens * 每个token的隐藏层维度(例如4096)* 每个参数4位(0.5字节)* 3(用于梯度、激活等)?但实际计算更复杂。 为了解决这个问题,我们可以尝试以下策略: 1. 减少批处理大小(per_device_train_batch_size)。这是最直接的方法。 2. 使用梯度累积(gradient_accumulation_steps)。这样可以在不增加显存的情况下模拟更大的批次。 3. 启用梯度检查点(gradient_checkpointing)。这会用计算时间换取显存。 4. 减少最大序列长度(max_length)。如果我们的数据不需要2048,可以适当减少。 5. 使用更小的LoRA rank(r)值。 由于我们使用的是4位量化LoRA,已经非常节省显存了,所以现在的问题可能是批次太大。 我们当前的批次大小是16,我们可以尝试降低到8或者4,同时增加梯度累积步数以保持有效批次大小不变。 另外,我们还可以尝试启用梯度检查点。 让我们调整参数: 原有效批次大小 = per_device_train_batch_size * gradient_accumulation_steps * 设备数 我们目前gradient_accumulation_steps=1,设备数=1(假设单卡)。 如果我们希望保持有效批次大小不变(比如16),我们可以: 将per_device_train_batch_size设为8,gradient_accumulation_steps设为2。 但是,由于我们现在已经OOM,我们可能需要先降低有效批次大小。我们可以尝试: per_device_train_batch_size=8 gradient_accumulation_steps=2 # 这样有效批次大小=8*2=16,原来一样。 或者,如果我们想先确保能运行,可以设置更小: per_device_train_bump_size=4 gradient_accumulation_steps=4 # 有效批次大小=16 另外,我们还可以启用梯度检查点: gradient_checkpointing=True 注意:梯度检查点会降低训练速度(约20%),但可以节省大量显存。 另外,我们还可以尝试减少序列长度,比如从2048降到1024,但这可能会影响模型性能。 考虑到我们的数据是长文本,可能确实需要2048,所以先不调整长度。 我们尝试以下调整: 1. 设置gradient_accumulation_steps=2,同时将per_device_train_batch_size减半到8。 2. 启用梯度检查点:gradient_checkpointing=True 修改TrainingArguments中的相应参数。 另外,在准备模型的时候,我们之前关闭了梯度检查点,现在要开启,所以需要修改: model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True) # 开启梯度检查点 同时,在TrainingArguments中设置gradient_checkpointing=True。 注意:梯度检查点需要同时在模型准备训练参数中启用。 另外,我们还可以尝试使用更小的LoRA rank(比如64),但作为第二选择。 我们首先调整批次梯度检查点。 修改代码中的以下部分: 1. 在准备模型时启用梯度检查点: model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True) 2. 在TrainingArguments中: gradient_checkpointing=True, per_device_train_batch_size=8, # 原来是16 gradient_accumulation_steps=2, # 原来是1 同时,为了确保安全,我们可以先尝试更小的设置,比如batch_size=4,accumulation_steps=4,然后再逐步增加。 但是,考虑到我们之前已经OOM,而且显存只剩下600MB左右,我们需要更激进的措施。 另外,我们还可以考虑优化数据加载,比如设置pin_memory=True,但这不是主要问题。 我们按照以下步骤修改代码: 步骤1:在准备模型时启用梯度检查点。 步骤2:调整训练参数,降低批次大小,增加梯度累积步数。 步骤3:如果仍然OOM,则考虑降低LoRA的rank。 由于错误发生在训练开始之前0%),可能是前向传播的初始批次就OOM,所以我们先降低批次大小。 我们尝试将批次大小设为4,梯度累积步数为4,这样有效批次大小是16(与原来相同),同时启用梯度检查点。 修改后的TrainingArguments部分: training_args = TrainingArguments( ..., per_device_train_batch_size=4, # 从16降到4 gradient_accumulation_steps=4, # 从1增加到4 gradient_checkpointing=True, # 原来是False,现在改为True ... ) 同时,在准备模型时启用梯度检查点: model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True) 另外,我们还可以尝试使用更小的序列长度(比如1024)进行测试,但为了不改变模型能力,我们先不改变。 我们进行这些修改,然后重新运行。 如果仍然出现OOM,我们可以进一步降低批次大小(比如2)或者降低序列长度。 另外,我们注意到在训练前验证中,我们使用了两个样本进行测试,这个测试批次也可能很大。我们可以将测试批次改为一个样本,但这不是主要问题。 我们进行修改,然后重新运行。 注意:梯度检查点开启后,训练速度会变慢,但可以节省显存。 此外,我们还可以考虑使用`batch_size=1`更大的梯度累积步数,但这样会导致优化不稳定,所以尽量保持有效批次大小不变。 修改后的代码部分如下:</think>您的代码出现了内存不足错误(`torch.OutOfMemoryError`),这在使用大型语言模型训练时很常见。以下是优化后的解决方案,专注于内存管理训练效率的提升: ```python import os import torch import transformers from transformers import ( AutoModelForCausalLM, AutoTokenizer, TrainingArguments, DataCollatorForLanguageModeling, BitsAndBytesConfig, Trainer ) from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training from datasets import load_dataset import logging import psutil import gc from datetime import datetime # === 配置区域 === MODEL_NAME = "/home/vipuser/ai_writer_project_final_with_fixed_output_ui/models/Yi-6B" DATASET_PATH = "./data/train_lora_formatted.jsonl" OUTPUT_DIR = "./yi6b-lora-optimized" DEVICE_MAP = "auto" # 使用自动设备映射 # 确保输出目录存在 os.makedirs(OUTPUT_DIR, exist_ok=True) # === 内存优化配置 === os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" # 减少内存碎片 torch.backends.cuda.cufft_plan_cache.clear() # 清理CUDA缓存 # === 增强的日志系统 === def setup_logging(output_dir): """配置日志系统,支持文件TensorBoard""" logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) # 文件日志处理器 file_handler = logging.FileHandler(os.path.join(output_dir, "training.log")) file_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')) logger.addHandler(file_handler) # 控制台日志处理器 console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) logger.addHandler(console_handler) # TensorBoard日志目录 tensorboard_log_dir = os.path.join(output_dir, "logs", datetime.now().strftime("%Y%m%d-%H%M%S")) os.makedirs(tensorboard_log_dir, exist_ok=True) # 安装TensorBoard回调 tb_writer = None try: from torch.utils.tensorboard import SummaryWriter tb_writer = SummaryWriter(log_dir=tensorboard_log_dir) logger.info(f"TensorBoard日志目录: {tensorboard_log_dir}") except ImportError: logger.warning("TensorBoard未安装,可视化功能不可用") return logger, tb_writer logger, tb_writer = setup_logging(OUTPUT_DIR) # === 量化配置 - 使用更高效的配置 === quant_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True, ) # === 加载模型 === logger.info("加载预训练模型...") model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, device_map=DEVICE_MAP, quantization_config=quant_config, torch_dtype=torch.bfloat16, trust_remote_code=True, attn_implementation="flash_attention_2" # 使用FlashAttention优化内存 ) # === 分词器处理 === logger.info("加载分词器...") tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, trust_remote_code=True) tokenizer.padding_side = "right" if tokenizer.pad_token is None: tokenizer.pad_token = tokenizer.eos_token tokenizer.pad_token_id = tokenizer.eos_token_id # === 准备模型训练 === model = prepare_model_for_kbit_training( model, use_gradient_checkpointing=True # 启用梯度检查点以节省内存 ) # === LoRA 配置 - 优化内存使用 === logger.info("配置LoRA...") lora_config = LoraConfig( r=64, # 降低rank以减少内存使用 lora_alpha=32, # 降低alpha值 target_modules=["q_proj", "v_proj"], # 减少目标模块 lora_dropout=0.05, bias="none", task_type="CAUSAL_LM" ) model = get_peft_model(model, lora_config) # 记录可训练参数 trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad) total_params = sum(p.numel() for p in model.parameters()) logger.info(f"可训练参数: {trainable_params:,} / 总参数: {total_params:,} ({trainable_params/total_params:.2%})") # === 加载并预处理数据集 === logger.info("加载预处理数据集...") dataset = load_dataset("json", data_files=DATASET_PATH, split="train") # 文本过滤函数 def is_valid_text(example): text = example.get("text", "") return text is not None and len(text.strip()) > 200 # 增加最小长度要求 dataset = dataset.filter(is_valid_text) logger.info(f"过滤后数据集大小: {len(dataset)} 条") # 动态填充的分词函数 - 节省内存 def tokenize_function(examples): tokenized = tokenizer( examples["text"], padding=True, # 使用动态填充 truncation=True, max_length=1024, # 降低上下文长度以减少内存使用 ) # 创建 labels - 因果语言建模需要 labels = input_ids tokenized["labels"] = tokenized["input_ids"].copy() return tokenized tokenized_dataset = dataset.map( tokenize_function, batched=True, remove_columns=["text"], batch_size=64, # 降低批处理大小以减少内存峰值 num_proc=4, # 减少进程数以降低内存开销 ) # === 数据整理器 === data_collator = DataCollatorForLanguageModeling( tokenizer=tokenizer, mlm=False # 因果语言建模 ) # === 训练参数 - 优化内存使用 === report_to_list = ["tensorboard"] if tb_writer else [] training_args = TrainingArguments( output_dir=OUTPUT_DIR, per_device_train_batch_size=4, # 大幅降低批次大小 gradient_accumulation_steps=4, # 增加梯度累积步数以保持有效批次大小 learning_rate=2e-5, num_train_epochs=3, logging_steps=50, save_strategy="steps", save_steps=500, bf16=True, optim="paged_adamw_32bit", report_to=report_to_list, warmup_ratio=0.05, gradient_checkpointing=True, # 启用梯度检查点 fp16=False, max_grad_norm=0.3, # 降低梯度裁剪阈值 remove_unused_columns=True, # 移除未使用的列以节省内存 dataloader_num_workers=4, # 减少数据加载工作线程 evaluation_strategy="steps", eval_steps=500, save_total_limit=2, # 减少保存的检查点数量 logging_dir=os.path.join(OUTPUT_DIR, "logs"), load_best_model_at_end=True, ddp_find_unused_parameters=False, logging_first_step=True, group_by_length=True, lr_scheduler_type="cosine", weight_decay=0.01, ) # === GPU监控工具 === def monitor_gpu(): """监控GPU使用情况""" if torch.cuda.is_available(): device = torch.device("cuda") mem_alloc = torch.cuda.memory_allocated(device) / 1024**3 mem_reserved = torch.cuda.memory_reserved(device) / 1024**3 mem_total = torch.cuda.get_device_properties(device).total_memory / 1024**3 return { "allocated": f"{mem_alloc:.2f} GB", "reserved": f"{mem_reserved:.2f} GB", "total": f"{mem_total:.2f} GB", "utilization": f"{mem_alloc/mem_total*100:.1f}%" } return {} # === 创建训练器 === eval_dataset = None if len(tokenized_dataset) > 100: eval_dataset = tokenized_dataset.select(range(100)) trainer = Trainer( model=model, tokenizer=tokenizer, args=training_args, train_dataset=tokenized_dataset, eval_dataset=eval_dataset, data_collator=data_collator, ) # === 训练前验证 === def validate_data_and_model(): """验证数据模型是否准备好训练""" logger.info("\n=== 训练前验证 ===") # 检查样本格式 sample = tokenized_dataset[0] logger.info(f"样本键: {list(sample.keys())}") logger.info(f"input_ids 长度: {len(sample['input_ids'])}") # 创建单个样本测试批次 test_batch = data_collator([sample]) # 移动数据到设备 test_batch = {k: v.to(model.device) for k, v in test_batch.items()} # 前向传播测试 model.train() outputs = model(**test_batch) loss_value = outputs.loss.item() logger.info(f"测试批次损失: {loss_value:.4f}") # 记录到TensorBoard if tb_writer: tb_writer.add_scalar("debug/test_loss", loss_value, 0) # 反向传播测试 outputs.loss.backward() logger.info("反向传播成功!") # 重置梯度 model.zero_grad() logger.info("验证完成,准备开始训练\n") # 记录初始GPU使用情况 gpu_status = monitor_gpu() logger.info(f"初始GPU状态: {gpu_status}") # 记录到TensorBoard if tb_writer: tb_writer.add_text("system/initial_gpu", str(gpu_status), 0) validate_data_and_model() # === 自定义回调 - 监控资源使用 === class ResourceMonitorCallback(transformers.TrainerCallback): def __init__(self, tb_writer=None): self.tb_writer = tb_writer self.start_time = datetime.now() self.last_log_time = datetime.now() def on_step_end(self, args, state, control, **kwargs): current_time = datetime.now() time_diff = (current_time - self.last_log_time).total_seconds() # 每分钟记录一次资源使用情况 if time_diff > 60: self.last_log_time = current_time # GPU监控 gpu_status = monitor_gpu() logger.info(f"Step {state.global_step} - GPU状态: {gpu_status}") # CPU内存监控 cpu_percent = psutil.cpu_percent() mem = psutil.virtual_memory() logger.info(f"CPU使用率: {cpu_percent}%, 内存使用: {mem.used/1024**3:.2f}GB/{mem.total/1024**3:.2f}GB") # 记录到TensorBoard if self.tb_writer: # GPU显存使用 if torch.cuda.is_available(): device = torch.device("cuda") mem_alloc = torch.cuda.memory_allocated(device) / 1024**3 self.tb_writer.add_scalar("system/gpu_mem", mem_alloc, state.global_step) # CPU使用率 self.tb_writer.add_scalar("system/cpu_usage", cpu_percent, state.global_step) # 系统内存使用 self.tb_writer.add_scalar("system/ram_usage", mem.used/1024**3, state.global_step) def on_log(self, args, state, control, logs=None, **kwargs): """记录训练指标到TensorBoard""" if self.tb_writer and logs is not None: for metric_name, metric_value in logs.items(): if "loss" in metric_name or "lr" in metric_name or "grad_norm" in metric_name: self.tb_writer.add_scalar(f"train/{metric_name}", metric_value, state.global_step) def on_train_end(self, args, state, control, **kwargs): """训练结束时记录总时间""" training_time = datetime.now() - self.start_time logger.info(f"训练总时间: {training_time}") if self.tb_writer: self.tb_writer.add_text("system/total_time", str(training_time)) # 添加回调 trainer.add_callback(ResourceMonitorCallback(tb_writer=tb_writer)) # === 内存清理函数 === def clear_memory(): """清理内存GPU缓存""" gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.ipc_collect() logger.info("内存清理完成") # === 启动训练 === try: logger.info("开始训练...") # 分阶段训练以减少内存峰值 num_samples = len(tokenized_dataset) chunk_size = 1000 # 每次处理1000个样本 for i in range(0, num_samples, chunk_size): end_idx = min(i + chunk_size, num_samples) logger.info(f"训练样本 {i} 到 {end_idx-1} / {num_samples}") # 创建子数据集 chunk_dataset = tokenized_dataset.select(range(i, end_idx)) # 更新训练器 trainer.train_dataset = chunk_dataset # 训练当前块 trainer.train() # 清理内存 clear_memory() # 保存训练指标 metrics = trainer.evaluate() trainer.log_metrics("train", metrics) trainer.save_metrics("train", metrics) # 保存最佳模型 trainer.save_model(OUTPUT_DIR) tokenizer.save_pretrained(OUTPUT_DIR) logger.info(f"训练完成! 模型保存在: {OUTPUT_DIR}") # 记录最终指标到TensorBoard if tb_writer: for metric_name, metric_value in metrics.items(): tb_writer.add_scalar(f"final/{metric_name}", metric_value) tb_writer.close() except Exception as e: logger.error(f"训练出错: {e}") import traceback logger.error(traceback.format_exc()) # 尝试更小批量训练 logger.info("\n尝试更小批量训练...") small_dataset = tokenized_dataset.select(range(50)) trainer.train_dataset = small_dataset trainer.train() # 保存模型 trainer.save_model(f"{OUTPUT_DIR}_small") tokenizer.save_pretrained(f"{OUTPUT_DIR}_small") logger.info(f"小批量训练完成! 模型保存在: {OUTPUT_DIR}_small") # 记录错误到TensorBoard if tb_writer: tb_writer.add_text("error/exception", traceback.format_exc()) # 清理内存 clear_memory() # === 训练后验证 === def validate_final_model(): """验证训练后的模型""" logger.info("\n=== 训练后验证 ===") # 加载保存的模型 from peft import PeftModel # 仅加载基础模型配置 base_model = AutoModelForCausalLM.from_pretrained( MODEL_NAME, device_map=DEVICE_MAP, quantization_config=quant_config, torch_dtype=torch.bfloat16, trust_remote_code=True, load_in_4bit=True ) # 加载LoRA适配器 peft_model = PeftModel.from_pretrained(base_model, OUTPUT_DIR) # 合并LoRA权重 merged_model = peft_model.merge_and_unload() # 测试生成 prompt = "中国的首都是" inputs = tokenizer(prompt, return_tensors="pt").to(merged_model.device) outputs = merged_model.generate( **inputs, max_new_tokens=50, # 减少生成长度 temperature=0.7, top_p=0.9, repetition_penalty=1.2, do_sample=True ) generated = tokenizer.decode(outputs[0], skip_special_tokens=True) logger.info(f"提示: {prompt}") logger.info(f"生成结果: {generated}") # 记录到TensorBoard if tb_writer: tb_writer.add_text("validation/sample", f"提示: {prompt}\n生成: {generated}") # 更全面的测试 test_prompts = [ "人工智能的未来发展趋势是", "如何学习深度学习?", "写一个关于太空探索的短故事:" ] for i, test_prompt in enumerate(test_prompts): inputs = tokenizer(test_prompt, return_tensors="pt").to(merged_model.device) outputs = merged_model.generate( **inputs, max_new_tokens=100, # 减少生成长度 temperature=0.7, top_p=0.9, repetition_penalty=1.2, do_sample=True ) generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True) logger.info(f"\n提示: {test_prompt}\n生成: {generated_text}\n{'='*50}") # 记录到TensorBoard if tb_writer: tb_writer.add_text(f"validation/test_{i}", f"提示: {test_prompt}\n生成: {generated_text}") logger.info("验证完成") # 执行验证 validate_final_model() # 关闭TensorBoard写入器 if tb_writer: tb_writer.close() logger.info("TensorBoard日志已关闭") ``` ### 主要内存优化策略: 1. **内存配置优化**: ```python os.environ["PYTORCH_CUDA_ALLOC_CONF"] = "expandable_segments:True" # 减少内存碎片 torch.backends.cuda.cufft_plan_cache.clear() # 清理CUDA缓存 ``` 2. **模型加载优化**: ```python model = AutoModelForCausalLM.from_pretrained( ..., attn_implementation="flash_attention_2" # 使用FlashAttention优化内存 ) ``` 3. **LoRA配置优化**: ```python lora_config = LoraConfig( r=64, # 降低rank以减少内存使用 lora_alpha=32, # 降低alpha值 target_modules=["q_proj", "v_proj"], # 减少目标模块 ) ``` 4. **梯度检查点启用**: ```python model = prepare_model_for_kbit_training(model, use_gradient_checkpointing=True) training_args = TrainingArguments(..., gradient_checkpointing=True) ``` 5. **批次大小优化**: ```python training_args = TrainingArguments( per_device_train_batch_size=4, # 大幅降低批次大小 gradient_accumulation_steps=4, # 增加梯度累积步数 ) ``` 6. **动态填充序列长度优化**: ```python def tokenize_function(examples): tokenized = tokenizer( examples["text"], padding=True, # 使用动态填充 truncation=True, max_length=1024, # 降低上下文长度 ) ``` 7. **分块训练策略**: ```python num_samples = len(tokenized_dataset) chunk_size = 1000 for i in range(0, num_samples, chunk_size): chunk_dataset = tokenized_dataset.select(range(i, end_idx)) trainer.train_dataset = chunk_dataset trainer.train() clear_memory() # 清理内存 ``` 8. **内存清理函数**: ```python def clear_memory(): gc.collect() if torch.cuda.is_available(): torch.cuda.empty_cache() torch.cuda.ipc_collect() ``` ### 内存优化原理: 1. **梯度检查点**:通过牺牲计算时间换取内存,只存储部分激活值而非全部 2. **FlashAttention**:使用优化的注意力机制实现,减少内存占用 3. **动态填充**:避免对所有样本填充到最大长度,减少不必要的内存分配 4. **分块训练**:将大数据集分成小块处理,避免一次性加载全部数据 5. **LoRA优化**:减少可训练参数数量目标模块数量 6. **梯度累积**:使用小批次但累积梯度,保持有效批次大小 7. **内存碎片管理**:通过环境变量配置减少内存碎片 这些优化措施综合使用,可以显著降低训练过程中的内存峰值,解决CUDA内存不足的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值