使用PEFT库进行ChatGLM3-6B模型的QLORA高效微调

PEFT库进行ChatGLM3-6B模型QLORA高效微调

  • QLORA微调ChatGLM3-6B模型
    • 安装相关库
    • 使用ChatGLM3-6B
    • 模型GPU显存占用
    • 准备数据集
    • 加载数据集
    • 数据处理
    • 数据集处理
    • 加载量化模型-4bit
    • 预处理量化模型
    • 配置LoRA适配器
    • 训练超参数配置
    • 开始训练
    • 保存LoRA模型
    • 模型推理
    • 合并模型
    • 使用微调后的模型

QLORA微调ChatGLM3-6B模型

LoRA的核心思想是将可调整的低秩矩阵注入到Transformer架构的每一层中,充当"适配器"的作用。这样可以使模型针对特定任务进行调整和专门化,同时最大限度地减少额外的参数数量,提高参数效率。

QLoRA是LoRA的扩展版本,在微调过程中引入了量化技术,以进一步提高参数效率。QLoRA利用LoRA的原理,并引入了4位NormalFloat(NF4)量化和双重量化技术,进一步减少了存储和计算资源的使用。

安装相关库

pip install datasets==2.17.0 pandas transformers==4.37.2 peft==0.8.0  accelerate==0.27.0 bitsandbytes autoawq optimum

使用ChatGLM3-6B

直接调用ChatGLM3-6B模型来生成对话

from transformers import AutoTokenizer, AutoModelmodel_id = "/root/work/chatglm3-6b"
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
#model = AutoModel.from_pretrained(model_id, trust_remote_code=True).half().cuda()
model = AutoModel.from_pretrained(model_id, trust_remote_code=True, device='cuda')model = model.eval()
response, history = model.chat(tokenizer, "你好", history=history)
print(response)

在这里插入图片描述

模型GPU显存占用

默认情况下,模型以半精度(float16)加载,模型权重需要大概 13GB显存。

获取当前模型占用的GPU显存

memory_bytes = model.get_memory_footprint()
# 转换为GB
memory_gb = memory_bytes / (1024 ** 3)  
print(f"{memory_gb :.2f}GB")

注意:

与实际进程占用有差异,差值为预留给PyTorch的显存

准备数据集

准备数据集其实就是指令集构建,LLM的微调一般指指令微调过程。所谓指令微调,就是使用的微调数据格式、形式。

训练目标是让模型具有理解并遵循用户指令的能力。因此在指令集构建时,应该针对目标任务,针对性的构建任务指令集。

[{"instruction": "用户指令(必填)","input": "用户输入(选填)","output": "模型回答(必填)",}
]
instruction:用户指令,告知模型其需要完成的任务input:用户输入,是完成用户指令所必须的输入内容output:模型回答,模型应该给出的输出

这里根据企业私有文档数据,生成相关格式的训练数据集,大概格式如下:

[  {"instruction": "内退条件是什么?","input": "","output": "内退条件包括与公司签订正式劳动合同并连续工作满20年及以上,以及距离法定退休年龄不足5年。特殊工种符合国家相关规定可提前退休的也可在退休前5年内提出内退申请。"},
]

作者:CodeDevMaster
链接:https://juejin.cn/post/7384636970033938482
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

加载数据集

from datasets import load_dataset  # 导入 load_dataset 函数# 加载处理好的训练数据集
dataset = load_dataset("json", data_files="./train_data.json")
print(dataset)  # 打印加载的数据集对象
DatasetDict({train: Dataset({features: ['instruction', 'output'],num_rows: 20182})
})

数据处理

Lora训练数据是需要经过tokenize编码处理,然后后再输入模型进行训练。一般需要将输入文本编码为input_ids,将输出文本编码为labels,编码之后的结果都是多维的向量。

加载AutoTokenizer

from transformers import AutoTokenizer  # 导入 AutoTokenizer 类# 使用 AutoTokenizer 类的 from_pretrained 方法加载指定模型的 tokenizer
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm3-6b", trust_remote_code=True)

需要定义一个预处理函数,这个函数用于对每一个样本,编码其输入、输出文本并返回一个编码后的字典。

# tokenize_func 函数
def tokenize_func(example, tokenizer, ignore_label_id=-100):"""对单个数据样本进行tokenize处理。参数:example (dict): 包含'content'和'summary'键的字典,代表训练数据的一个样本。tokenizer (transformers.PreTrainedTokenizer): 用于tokenize文本的tokenizer。ignore_label_id (int, optional): 在label中用于填充的忽略ID,默认为-100。返回:dict: 包含'tokenized_input_ids'和'labels'的字典,用于模型训练。"""prompt_text = ''                          # 所有数据前的指令文本max_input_length = 512                    # 输入的最大长度max_output_length = 1536                  # 输出的最大长度# 构建问题文本question = prompt_text + example['instruction']if example.get('input', None) and example['input'].strip():question += f'\n{example["input"]}'# 构建答案文本answer = example['output']# 对问题和答案文本进行tokenize处理q_ids = tokenizer.encode(text=question, add_special_tokens=False)a_ids = tokenizer.encode(text=answer, add_special_tokens=False)# 如果tokenize后的长度超过最大长度限制,则进行截断if len(q_ids) > max_input_length - 2:  # 保留空间给gmask和bos标记q_ids = q_ids[:max_input_length - 2]if len(a_ids) > max_output_length - 1:  # 保留空间给eos标记a_ids = a_ids[:max_output_length - 1]# 构建模型的输入格式input_ids = tokenizer.build_inputs_with_special_tokens(q_ids, a_ids)question_length = len(q_ids) + 2  # 加上gmask和bos标记# 构建标签,对于问题部分的输入使用ignore_label_id进行填充labels = [ignore_label_id] * question_length + input_ids[question_length:]return {'input_ids': input_ids, 'labels': labels}

可以从从原始数据集中提取出一个较小规模的训练子集,以便稍后对这个子集进行处理、训练或其他操作。

from datasets import DatasetDict  # 导入 DatasetDict 类small_dataset = DatasetDict()  # 创建一个空的 DatasetDict 对象# 在 small_dataset 中存储一个部分数据集,包括以下步骤:
# 1. 从原始数据集的 'train' 部分中随机打乱数据(使用随机种子 16)
# 2. 选择前 1000 条样本构成一个小的数据集
small_dataset["train"] = dataset["train"].shuffle(seed=16).select(range(1000))# 打印数据集
print(small_dataset)

进行数据映射处理,同时删除特定列

# 获取 'train' 部分的列名
column_names = dataset['train'].column_names  # 使用lambda函数调用tokenize_func函数,并传入example和tokenizer作为参数
tokenized_dataset = small_dataset['train'].map(lambda example: tokenize_func(example, tokenizer),batched=False,  # 不按批次处理remove_columns=column_names  # 移除特定列(column_names中指定的列)
)

查看处理结果

print(tokenized_dataset[0])

在这里插入图片描述

数据集处理

需要使用一个数据收集器,可以使用transformers 中的DataCollatorForSeq2Seq数据收集器

from transformers import DataCollatorForSeq2Seqdata_collator = DataCollatorForSeq2Seq(tokenizer,model=model,label_pad_token_id=-100,pad_to_multiple_of=None,padding=True
)

或者自定义实现一个数据收集器

import torch
from typing import List, Dict, Optional# DataCollatorForChatGLM 类
class DataCollatorForChatGLM:"""用于处理批量数据的DataCollator,尤其是在使用 ChatGLM 模型时。该类负责将多个数据样本(tokenized input)合并为一个批量,并在必要时进行填充(padding)。属性:pad_token_id (int): 用于填充(padding)的token ID。max_length (int): 单个批量数据的最大长度限制。ignore_label_id (int): 在标签中用于填充的ID。"""def __init__(self, pad_token_id: int, max_length: int = 2048, ignore_label_id: int = -100):"""初始化DataCollator。参数:pad_token_id (int): 用于填充(padding)的token ID。max_length (int): 单个批量数据的最大长度限制。ignore_label_id (int): 在标签中用于填充的ID,默认为-100。"""self.pad_token_id = pad_token_idself.ignore_label_id = ignore_label_idself.max_length = max_lengthdef __call__(self, batch_data: List[Dict[str, List]]) -> Dict[str, torch.Tensor]:"""处理批量数据。参数:batch_data (List[Dict[str, List]]): 包含多个样本的字典列表。返回:Dict[str, torch.Tensor]: 包含处理后的批量数据的字典。"""# 计算批量中每个样本的长度len_list = [len(d['input_ids']) for d in batch_data]batch_max_len = max(len_list)  # 找到最长的样本长度input_ids, labels = [], []for len_of_d, d in sorted(zip(len_list, batch_data), key=lambda x: -x[0]):pad_len = batch_max_len - len_of_d  # 计算需要填充的长度# 添加填充,并确保数据长度不超过最大长度限制ids = d['input_ids'] + [self.pad_token_id] * pad_lenlabel = d['labels'] + [self.ignore_label_id] * pad_lenif batch_max_len > self.max_length:ids = ids[:self.max_length]label = label[:self.max_length]input_ids.append(torch.LongTensor(ids))labels.append(torch.LongTensor(label))# 将处理后的数据堆叠成一个tensorinput_ids = torch.stack(input_ids)labels = torch.stack(labels)return {'input_ids': input_ids, 'labels': labels}data_collator = DataCollatorForChatGLM(pad_token_id=tokenizer.pad_token_id)

加载量化模型-4bit

对于加载4bit量化模型,需要设置多个参数,具体参数如下:使用nf4量化数据类型加载模型,开启双量化配置,以bf16混合精度训练

以4-bits量化加载ChatGLM3-6B模型,只需要大约4GB左右显存。

from transformers import AutoModel, BitsAndBytesConfig# QLoRA量化配置
q_config = BitsAndBytesConfig(load_in_4bit=True, # 是否在4位精度下加载模型bnb_4bit_quant_type='nf4', # 4位精度量化的类型,表示使用nf4量化类型bnb_4bit_use_double_quant=True,  # 是否使用双精度量化bnb_4bit_compute_dtype=torch.float16) # 4位精度计算的数据类型,使用半精度浮点数model = AutoModel.from_pretrained('./chatglm3-6b',quantization_config=q_config,device_map='auto',trust_remote_code=True)# 优化内存使用和计算效率
model.supports_gradient_checkpointing = True  
model.gradient_checkpointing_enable()
model.enable_input_require_grads()
model.config.use_cache = False 

预处理量化模型

预处理量化后的模型,使其可以支持低精度微调训练

peft库中的prepare_model_for_kbit_training函数是用于为模型执行量化准备的工具。这个函数通常用于准备在低比特宽度(如8-bit, 4-bit, 2-bit等)下进行模型训练或微调。

该函数的主要功能是调整模型的权重和激活,使其适应在指定的比特位宽度下进行有效的训练。

from peft import prepare_model_for_kbit_trainingkbit_model = prepare_model_for_kbit_training(model)

配置LoRA适配器

在peft中使用LoRA非常简单。借助PeftModel抽象,可以快速将低秩适配器(LoRA)应用到任意模型中。

在初始化相应的微调配置类(LoraConfig)时,需要显式指定在哪些层新增适配器(Adapter),并将其设置正确。

ChatGLM3-6B模型通过以下方式获取需要训练的模型层的名字

from peft.utils import TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPINGtarget_modules = TRANSFORMERS_MODELS_TO_LORA_TARGET_MODULES_MAPPING['chatglm']

在PEFT库的 constants.py 文件中定义了不同的 PEFT 方法,在各类大模型上的微调适配模块。

在这里插入图片描述

lora_config = LoraConfig(target_modules=target_modules,r=4,# LoRA秩lora_alpha=32,lora_dropout=0.05,bias='none',inference_mode=False,task_type=TaskType.CAUSAL_LM
)# 使用get_peft_model函数和给定的配置来获取一个PEFT模型
qlora_model = get_peft_model(kbit_model, lora_config)# 打印出模型中可训练的参数
qlora_model.print_trainable_parameters()

训练超参数配置

训练总步数steps的计算:

epoch: 1个epoch表示对全部训练集样本进行一次完整的训练num_train_epochs:多少个epoch, 表示进行多少个、多少轮完整数据集的训练

每个epoch的训练步数:

steps/epoch = num_train_examples / (batch_size * gradient_accumulation_steps)

训练总步数:

total_steps = steps/epoch * num_train_epochs
from transformers import TrainingArguments, Trainertraining_args = TrainingArguments(output_dir="models/chatglm3-6b",          # 输出目录per_device_train_batch_size=4,                     # 每个设备的训练批量大小gradient_accumulation_steps=4,                     # 梯度累积步数# per_device_eval_batch_size=8,                      # 每个设备的评估批量大小learning_rate=1e-3,                                # 学习率num_train_epochs=1,                                # 训练轮数lr_scheduler_type="linear",                        # 学习率调度器类型warmup_ratio=0.1,                                  # 预热比例logging_steps=10,                                 # 日志记录步数save_strategy="steps",                             # 模型保存策略save_steps=100,                                    # 模型保存步数# evaluation_strategy="steps",                       # 评估策略# eval_steps=500,                                    # 评估步数optim="adamw_torch",                               # 优化器类型fp16=True,                                        # 是否使用混合精度训练
)

启用指标评估

from transformers import TrainingArguments, Trainertraining_args = TrainingArguments(output_dir="models/chatglm3-6b",          # 输出目录per_device_train_batch_size=4,                     # 每个设备的训练批量大小gradient_accumulation_steps=4,                     # 梯度累积步数learning_rate=1e-3,                                # 学习率max_steps=100,                                     # 训练步数lr_scheduler_type="linear",                        # 学习率调度器类型warmup_ratio=0.1,                                  # 预热比例logging_steps=10,                                 # 日志记录步数save_strategy="steps",                             # 模型保存策略save_steps=20,                                    # 模型保存步数optim="adamw_torch",                               # 优化器类型fp16=True,                                        # 是否使用混合精度训练
)

开始训练

trainer = Trainer(model=qlora_model,args=training_args,train_dataset=tokenized_dataset,data_collator=data_collator)trainer.train()

保存LoRA模型

lora_model_path = "lora/chatglm3-6b"
trainer.model.save_pretrained(lora_model_path )
#model.save_pretrained(lora_model_path )

在这里插入图片描述

模型推理

使用QLoRA微调后的ChatGLM3-6B模型进行对比模型推理

import torch
from transformers import AutoModel, AutoTokenizer, BitsAndBytesConfig
from peft import PeftModel, PeftConfig# QLoRA量化配置
q_config = BitsAndBytesConfig(load_in_4bit=True,bnb_4bit_quant_type='nf4',bnb_4bit_use_double_quant=True,bnb_4bit_compute_dtype=torch.float32)# 加载量化后的原始模型
base_model = AutoModel.from_pretrained('./chatglm3-6b',quantization_config=q_config,trust_remote_code=True,device_map='auto')
# 优化
base_model.requires_grad_(False)
base_model.eval()# 加载tokenizer 
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path, trust_remote_code=True)

使用原始ChatGLM3-6B模型进行推理测试

input_text = 'xxxx?'
response, history = base_model.chat(tokenizer=tokenizer, query=input_text)
print(f'ChatGLM3-6B 微调前:\n{response}')

使用PeftModel合并源model与PEFT微调后的参数,然后进行推理测试

input_text = 'xxxx?'
# 合并
model = PeftModel.from_pretrained(base_model, './chatglm3-6b-lora')
response, history = model.chat(tokenizer=tokenizer, query=input_text)
print(f'ChatGLM3-6B 微调后: \n{response}')

合并模型

将lora权重合并到大模型中,将模型参数加载为16位浮点数

from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch model_path="./chatglm3-6b"
peft_model_path="./lora/chatglm3-6b"
save_path = "chatglm3-6b-lora"tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_path, trust_remote_code=True, low_cpu_mem_usage=True, torch_dtype=torch.float16, device_map="auto")
model = PeftModel.from_pretrained(model, peft_model_path)
model = model.merge_and_unload()tokenizer.save_pretrained(save_path)
model.save_pretrained(save_path)

查看合并文件
在这里插入图片描述

使用微调后的模型

from transformers import AutoTokenizer, AutoModeltokenizer = AutoTokenizer.from_pretrained("chatglm3-6b-lora", trust_remote_code=True)
model = AutoModel.from_pretrained("chatglm3-6b-lora", trust_remote_code=True, device='cuda')model = model.eval()
response, history = model.chat(tokenizer, "内退条件是什么?", history=[])
print(response)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/374140.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

禁止使用存储过程

优质博文:IT-BLOG-CN 灵感来源 什么是存储过程 存储过程Stored Procedure是指为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户可通过指定存储过程的名字并给定参数(如果该存储过程带有参数)来调用执行。 …

前端vue 实现取色板 的选择

大概就是这样的 一般的web端框架 都有自带的 的 比如 ant-design t-design 等 前端框架 都是带有这个的 如果遇到没有的我们可以自己尝试开发一下 简单 的 肯定比不上人家的 但是能用 能看 说的过去 我直接上代码了 其实这个取色板 就是一个input type 是color 的input …

一文清晰了解CSS——简单实例

首先一个小技巧: 一定要学会的vsCode格式化整理代码的快捷键,再也不用手动调格式了-腾讯云开发者社区-腾讯云 (tencent.com) CSS选择器用于选择要应用样式的HTML元素。常见的选择器包括: 类选择器:以.开头,用于选择具…

Paimon下载使用和基础操作说明

简介 Apache Paimon 是一种湖格式,支持使用 Flink 和 Spark 构建实时湖仓一体架构 用于流式处理和批处理操作。Paimon创新性地将湖格式与LSM(Log-structured merge-tree)相结合 结构,将实时流式更新引入 Lake 架构。 Paimon提供以…

C语言 | Leetcode C语言题解之第228题汇总区间

题目&#xff1a; 题解&#xff1a; char** summaryRanges(int* nums, int numsSize, int* returnSize) {char** ret malloc(sizeof(char*) * numsSize);*returnSize 0;int i 0;while (i < numsSize) {int low i;i;while (i < numsSize && nums[i] nums[i …

科普文:jvm笔记

一、JVM概述# 1. JVM内部结构# 跨语言的平台&#xff0c;只要遵循编译出来的字节码的规范&#xff0c;都可以由JVM运行 虚拟机 系统虚拟机 VMvare 程序虚拟机 JVM JVM结构 HotSpot虚拟机 详细结构图 前端编译器是编译为字节码文件 执行引擎中的JIT Compiler编译器是把字节…

Pycharm python解释器 unsupported python 3.1 解决

Pycharm 环境 unsupported python 3.1解决 1. 问题重现2. 原因分析3. 解决方法 1. 问题重现 之前使用Pycharm 2024.1.1的时候&#xff0c;环境配置的Python 3.11.9&#xff0c;现在改成使用Pycharm 2020.2.2&#xff0c;结果Python解释器显示“unsupported python 3.1”&#…

PyTorch复现PointNet——模型训练+可视化测试显示

因为项目涉及到3D点云项目&#xff0c;故学习下PointNet这个用来处理点云的神经网络 论文的话&#xff0c;大致都看了下&#xff0c;网络结构有了一定的了解&#xff0c;本博文主要为了下载调试PointNet网络源码&#xff0c;训练和测试调通而已。 我是在Anaconda下创建一个新的…

排序相关算法--1.插入排序+冒泡排序回顾

1.基本分类 2.插入排序 特点&#xff1a;有实践意义&#xff08;例如后期快排的优化&#xff09;&#xff0c;适应性强&#xff0c;一般不会到时间复杂度最坏的情况。 将第一个元素视为已经排好序的序列。取出下一个元素&#xff0c;在已经排好序的序列中从后往前比较&#xf…

硅纪元AI应用推荐 | 百度橙篇成新宠,能写万字长文

“硅纪元AI应用推荐”栏目&#xff0c;为您精选最新、最实用的人工智能应用&#xff0c;无论您是AI发烧友还是新手&#xff0c;都能在这里找到提升生活和工作的利器。与我们一起探索AI的无限可能&#xff0c;开启智慧新时代&#xff01; 百度橙篇&#xff0c;作为百度公司在202…

算法通关:004_1选择排序

代码一定要自己手敲理解 public class _004 {//选择排序&#xff0c;冒泡排序&#xff0c;插入排序//交换public static void swap(int[] arr,int i ,int j){int temp arr[i];arr[i] arr[j];arr[j] temp;}//选择排序public static void selectSort(int[] arr){if(arr null…

0基础学会在亚马逊云科技AWS上搭建生成式AI云原生Serverless问答QA机器人(含代码和步骤)

小李哥今天带大家继续学习在国际主流云计算平台亚马逊云科技AWS上开发生成式AI软件应用方案。上一篇文章我们为大家介绍了&#xff0c;如何在亚马逊云科技上利用Amazon SageMaker搭建、部署和测试开源模型Llama 7B。下面我将会带大家探索如何搭建高扩展性、高可用的完全托管云原…

初次用bable遍历vue项目下的中文

利用 babel 找到 AST 中的中文 // vite-plugin-babel-transform.js const parser require(babel/parser) const traverse require(babel/traverse).default // const types require(babel/types) // const generate require(babel/generator).default const fs require(f…

nginx的正向代理和反向代理

nginx的正向代理和反向代理 正向代理以及缓存配置&#xff1a; 代理&#xff1a;客户端不再是直接访问服务端&#xff0c;通过代理服务器访问服务端 正向代理&#xff1a;面向客户端&#xff0c;我们通过代理服务器的IP地址访问目标服务端。 服务端只知道代理服务器的地址&am…

公司内部配置GitLab,通过SSH密钥来实现免密clone、push等操作

公司内部配置GitLab&#xff0c;通过SSH密钥来实现免密clone、push等操作。以下是配置SSH密钥以实现免密更新的步骤&#xff1a; 1.生成SSH密钥 在本地计算机上打开终端或命令提示符。输入以下命令以生成一个新的SSH密钥&#xff1a;ssh-keygen -t rsa -b 4096 -C "your…

《C++设计模式》状态模式

文章目录 一、前言二、实现一、UML类图二、实现 一、前言 状态模式理解最基本上的我觉得应该也是够用了&#xff0c;实际用的话&#xff0c;也应该用的是Boost.MSM状态机。 相关代码可以在这里&#xff0c;如有帮助给个star&#xff01;AidenYuanDev/design_patterns_in_mode…

2970.力扣每日一题7/10 Java(暴力枚举)

博客主页&#xff1a;音符犹如代码系列专栏&#xff1a;算法练习关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 目录 解题思路 解题方法 时间复杂度 空间复杂度 Code 解题思路 incre…

Elastic Stack--15--聚合查询(SUM、MAX、MIN、AVG)案例

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 ES的聚合查询(SUM、MAX、MIN、AVG)1.求和查询2.求平均值3.最大最小值查询4.唯一值查询 (类似于sql中的distinct 去重)5.stats聚合 ES的聚合查询(SUM、MAX、MIN、AVG…

从零开始实现大语言模型(三):Token Embedding与位置编码

1. 前言 Embedding是深度学习领域一种常用的类别特征数值化方法。在自然语言处理领域&#xff0c;Embedding用于将对自然语言文本做tokenization后得到的tokens映射成实数域上的向量。 本文介绍Embedding的基本原理&#xff0c;将训练大语言模型文本数据对应的tokens转换成Em…

[leetcode]kth-smallest-element-in-a-sorted-matrix 有序矩阵中第k小元素

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool check(vector<vector<int>>& matrix, int mid, int k, int n) {int i n - 1;int j 0;int num 0;while (i > 0 && j < n) {if (matrix[i][j] < mid) {num i 1;j;…