news 2026/9/8 12:23:07

Qt表格大数据卡顿优化:QTableWidget到QTableView+自定义Model

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt表格大数据卡顿优化:QTableWidget到QTableView+自定义Model

简介:针对Qt开发中QTableWidget一次性加载大量数据导致界面卡顿的典型问题,这份资料提供基于惰性加载(Lazy Loading)优化的完整可运行工程。资源面向需要展示成百上千条表格记录的Qt初学者及中级开发者,核心实现封装为LazyLoadTableWidget类,通过自定义QAbstractTableModel模型控制数据按需读取,结合滚动条信号槽与多线程处理,在用户滚动到边界时才加载后续行,同时支持列级懒加载,显著降低内存占用与CPU负载。压缩包共12个文件,涵盖5个cpp源文件、4个h头文件以及pro工程配置,代码注释清晰、模块划分明确,便于直接复用或二次扩展,同时有助于深入理解模型/视图框架、工作线程与界面性能优化思路。已有1657人学习,适合希望掌握QTableWidget高效渲染机制、解决大数据量交互卡顿问题,并快速将惰性加载策略落地到实际项目的开发者。 做Qt桌面开发的,提到QTableWidget加载大量数据卡顿,应该都遇到过那种“一执行就白屏,转半天才出来”的场景。我自己接过好几个类似需求,几千行数据就明显掉帧,上万行直接卡死,拖动滚动条像拖一块砖头。这篇就把这个问题的根源、治标方案、根治方案和排查经验一次说清楚,代码都现成,可以直接拿去改。

1. 为什么QTableWidget一碰大数据就卡

1.1 卡顿根源:setItem逐格创建item的开销

先明确一点,QTableWidget本身不慢,慢在它的使用方式上。这个控件是“傻瓜式”的,你给它一个单元格,它就创建一个QTableWidgetItem对象,然后内部再维护这个表格矩阵。假设你要加载10000行、10列的数据,setItem就要执行10000乘以10等于10万次,也就是要new出10万个QTableWidgetItem对象。

10万个对象看起来不多,但QTableWidget内部还有大量信号通知、区域重绘、itemChanged判断等额外工作。更麻烦的是,如果你每次重新加载前调用clearContents()或者setRowCount(0),旧的item对象还要逐个delete销毁,销毁过程同样需要时间。一来一回,数据量只要上到几万行,你的界面刷新就卡到肉眼可见。

这个问题的本质是“将数据搬运进控件容器”的成本太高,而不是“展示数据”的成本高。QTableWidget适合那种几百上千行、手动维护的场景,数据量一上来,就会暴露性能瓶颈。

1.2 你没有意识到的隐藏开销

除了item对象本身的创建销毁,还有几个容易被忽略的卡顿因素,我在实际项目中基本都踩过:

  • 信号频繁触发。setItem、insertRow、setText这些操作都会触发cellChanged、itemChanged等信号。如果你在代码里连接了这些信号,并做了数据同步、界面联动、甚至数据库查询,那每一个单元格的变化都会执行一次槽函数,10000行数据就是10000次槽调用,卡顿直接翻倍。
  • 每插入一行自动重排。QTableWidget默认继承了QAbstractItemView的排序机制,如果你调用了setSortingEnabled(true)并且没有在数据填充完成后才启用,那么每次插入新行都可能触发排序操作。排序本身就是O(n*logn)级别的计算,再加上元素移动的信号风暴,全表卡死非常常见。
  • 滚动时列宽行高实时计算。对于QTableView/QTableWidget,如果列宽或行高没有统一设置,视图在滚动时会实时计算单元格大小。数据量大、单元格内容长度不同时,这个计算量会非常夸张。
  • 填充期间界面无法响应。大量setItem操作阻塞主线程,事件循环一直得不到执行,界面自然表现为“白屏卡住”。

知道这些根因后,解决思路就清晰了:要么减少对象创建次数,要么减少信号触发次数,要么把数据和视图彻底解耦,从根上避免逐格搬运。

2. 几分钟见效的“最小改动”方案

2.1 批量填充三件套:关闭刷新、屏蔽信号、合并操作

如果你不想重构代码,最快的方法是给原QTableWidget填充过程加一层“节流”。

void batchFillTable(QTableWidget* table, const QVector<QVector<QVariant>>& allData) { // 关闭视图刷新,停止重绘 table->setUpdatesEnabled(false); // 屏蔽全部信号,避免itemChanged等槽函数被反复触发 table->blockSignals(true); // 一次性设置行数,避免逐行insertRow table->clearContents(); table->setRowCount(allData.size()); const int columnCount = table->columnCount(); for (int row = 0; row < allData.size(); ++row) { const auto& rowData = allData.at(row); for (int col = 0; col < columnCount && col < rowData.size(); ++col) { QTableWidgetItem* item = new QTableWidgetItem(rowData.at(col).toString()); table->setItem(row, col, item); } } // 还原刷新和信号 table->blockSignals(false); table->setUpdatesEnabled(true); table->viewport()->update(); }

这段代码就是“治标”的典型:setUpdatesEnabled(false)让视图在填充期间不重绘;blockSignals(true)屏蔽所有Qt信号的通知槽函数调用;setRowCount一次性分配好所有行,避免insertRow逐行触发布局计算。填充完后统一viewport()->update(),界面只重绘一次。

我实测过,5000行8列的数据,不采用任何优化时耗时大概1.2秒,采用这个方案能压到200毫秒左右,提升非常明显。不过,当数据量超过2万行时,这招也会失效,因为QTableWidgetItem对象还是要创建10万个甚至更多,内存和对象管理的开销是躲不掉的。

2.2 表头加载的注意事项

别忘了表头本身也有开销。如果你在填充数据之前调用了setHorizontalHeaderLabels,并且表头有几十列,这个操作也可能成为局部卡顿点。建议把所有表头设置、列宽设置放在开启批量刷新之前一次完成,不要在填充循环中反复setColumnWidth、resizeColumnToContents。

另外,不要轻易调resizeColumnsToContents,这个操作会让表格逐列遍历全部行数据来计算内容宽高,10万行数据时极其缓慢。如果要自动列宽,建议:

  • 仅对少数关键列设置固定宽度。
  • 或者基于模型样例数据估算宽度,手动setColumnWidth。

2.3 排序必须延迟到数据加载完成

如果你需要支持点击表头排序,不要在填充数据前就开启setSortingEnabled(true)。正确做法是:

  1. 填充数据前调用setSortingEnabled(false)。
  2. 批量填充全部数据。
  3. 填充完成后调用setSortingEnabled(true)。

不然每插入一行,QTableWidget都会尝试对整个表重新排序,那个性能损耗在大数据量下是灾难级的。

3. 治本方案:用QTableView + 自定义Model替代QTableWidget

3.1 Model/View架构为什么更适合大数据

QTableWidget之所以慢,本质在于它是“View和Model绑定在一起”的简易组件。要根治大数据量卡顿,就得彻底切换到Qt的Model/View架构:视图负责显示,模型负责存数据,二者通过QModelIndex来交互。

视图在滚动时,只会向模型请求当前可见区域的单元格数据。也就是说,哪怕你有100万行数据,视图只显示20行,它就只调用20行左右的数据请求。只要你的模型data()方法查询足够快,界面滚动就会非常流畅,开销主要就是底部数据在内存那部分。

这是QTableWidget根本无法做到的,因为QTableWidget的数据本来就存储在item对象里,不管你看不看,所有item都已经创建好了。而且QTableWidget的滚动是“全表重绘模式”,哪怕只显示可视区域,它也要遍历所有行的信息来判断显示哪些行,这个遍历随着总行数增长而变慢。

从使用角度来说,你需要付出的代价是:自己维护一个数据容器,并实现QAbstractTableModel的几个纯虚函数。但这个代价在数据量超过1万行后,回报是很高的。

3.2 可落地的自定义Model实现

我直接给一个通用性很强的模板,你可以用结构体存储一行数据,使用QVector保存所有行。之所以用QVector而不是QList,因为需要连续内存访问,迭代性能更高。

struct TableRowData { QString name; QString status; QDateTime time; double value; }; class BigTableModel : public QAbstractTableModel { Q_OBJECT public: enum ColumnIndex { ColName = 0, ColStatus, ColTime, ColValue, ColumnCount }; explicit BigTableModel(QObject* parent = nullptr) : QAbstractTableModel(parent) { } int rowCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : m_rows.size(); } int columnCount(const QModelIndex& parent = QModelIndex()) const override { return parent.isValid() ? 0 : ColumnCount; } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { if (!index.isValid() || index.row() >= m_rows.size()) return QVariant(); const TableRowData& row = m_rows.at(index.row()); switch (role) { case Qt::DisplayRole: switch (index.column()) { case ColName: return row.name; case ColStatus: return row.status; case ColTime: return row.time.toString("yyyy-MM-dd hh:mm:ss"); case ColValue: return QString::number(row.value, 'f', 2); } break; case Qt::TextAlignmentRole: return int(Qt::AlignCenter); case Qt::ForegroundRole: if (index.column() == ColStatus && row.status == "异常") return QBrush(QColor(220, 50, 50)); break; } return QVariant(); } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { switch (section) { case ColName: return QStringLiteral("名称"); case ColStatus: return QStringLiteral("状态"); case ColTime: return QStringLiteral("发生时间"); case ColValue: return QStringLiteral("数值"); } } else { return section + 1; } return QVariant(); } void setRows(const QVector<TableRowData>& rows) { beginResetModel(); m_rows = rows; endResetModel(); } void appendRows(const QVector<TableRowData>& rows) { if (rows.isEmpty()) return; const int firstNewRow = m_rows.size(); beginInsertRows(QModelIndex(), firstNewRow, firstNewRow + rows.size() - 1); m_rows += rows; endInsertRows(); } const QVector<TableRowData>& rows() const { return m_rows; } private: QVector<TableRowData> m_rows; };

我这里只实现了DisplayRole和TextAlignmentRole、ForegroundRole,实际项目里如果要在单元格里加图标、背景色,可以在data()里增加DecorationRole、BackgroundRole分支。

3.3 视图端配合优化

有了model之后,视图的配置也需要注意几个点,否则还是跑不快。

auto* model = new BigTableModel(this); auto* tableView = new QTableView(this); tableView->setModel(model); tableView->setSelectionBehavior(QAbstractItemView::SelectRows); tableView->setSelectionMode(QAbstractItemView::SingleSelection); tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); tableView->setAlternatingRowColors(true); tableView->setSortingEnabled(false); // 需要排序再做自定义排序 tableView->setCornerButtonEnabled(false); // 关键优化:滚动粒度与行高缓存 tableView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); tableView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); tableView->setUniformRowHeights(true);

setUniformRowHeights(true)这个设置尤其关键,它告诉视图“所有行高度一致”,这样视图滚动时不需要逐行计算行高,可以直接用行号乘以固定行高来快速定位。如果你的行内容不会换行、不需要自适应高度,务必开启,它对大数据量滚动的流畅度影响极大。

QTableView + 自定义Model的加载速度,相比QTableWidget的setItem方案,在同数据量下可以达到数量级级别的差距。我自己用10万行验证,QTableWidget方案加载耗时大概8秒,QTableView+Model方案直接重置模型加一次全表刷新,耗时只是几十毫秒级别,滚动也流畅。

3.4 保留QTableWidget的“便捷功能”

如果你迁移到QTableView之后,发现原本用的右键菜单、单元格编辑、选中高亮等都要自己重新实现,这确实是个工作量。但换个角度看,QTableWidget内部策略对大数据不友好,你迟早得替换。实际项目中,我通常会把QTableWidget的常用“便捷操作”在自定义model里一并实现,比如:

  • 开启编辑:可以在data()里加ItemIsEditable flag,然后实现setData()方法。
  • 右键菜单:通过tableView的customContextMenuRequested信号实现,不依赖QTableWidget。
  • 拖拽、排序:实现sort()虚函数,配合sortByColumn信号。

一旦model实现得足够健壮,后续增加任何业务逻辑都更清晰,因为数据和界面已经解耦了。

4. 再进一步:大数据量下的三个实用增强

4.1 分页加载:即使自定义Model也别一把梭

虽然QTableView+Model不卡,但如果你一次性加载几十万行,内存里仍然存了所有行的QString和QDateTime对象,几十万行下来占用也能突破几百MB。这时候就得分页加载了。

最简单的分页策略是服务端分页,适用场景是每次需要展示的内容变化不大。如果你的数据源来自数据库或文件,可以在模型里保存当前页号和页大小,通过setRows()替换当前页数据:

void loadPage(int pageIndex, int pageSize) { // 耗时查询最好放到后台线程 QVector<TableRowData> pageData = m_dataSource->queryPage(pageIndex, pageSize); beginResetModel(); m_rows = pageData; endResetModel(); }

注意:如果是数据库查询,一定要把查询放到子线程里,只在主线程更新model,并且update完成后调用endResetModel()。否则查询期间界面还是会卡住。

4.2 懒加载:按需加载当前可见行

懒加载适合那种行与行之间数据获取代价不等的场景。例如第一列是文件名,后面几列需要解析文件内容才能填上。这种情况下,不要一开始就把所有内容解析完,而是优先加载可见区域和附近缓冲区的数据。

实现思路是利用视图滚动信号:

connect(tableView->verticalScrollBar(), &QScrollBar::valueChanged, this, [this]() { const int firstVisibleRow = tableView->rowAt(0); const int lastVisibleRow = tableView->rowAt(tableView->viewport()->height()); m_model->ensureRowsLoaded(firstVisibleRow - 50, lastVisibleRow + 50); });

ensureRowsLoaded负责检查指定范围是否已经加载,如果没有加载就启动后台任务填充,然后通过dataChanged信号触发局部重绘:

void ensureRowsLoaded(int startRow, int endRow) { if (startRow < 0) startRow = 0; if (endRow >= m_rows.size()) endRow = m_rows.size() - 1; if (startRow > endRow) return; QVector<int> columnsToUpdate; for (int r = startRow; r <= endRow; ++r) { if (m_rowLoaded[r]) continue; loadRowData(r); // 这里可能是异步的 m_rowLoaded[r] = true; columnsToUpdate.append(ColValue); } if (!columnsToUpdate.isEmpty()) { emit dataChanged(index(startRow, columnsToUpdate.first()), index(endRow, columnsToUpdate.last())); } }

需要注意,这里的加载如果比较耗时,建议用QtConcurrent或QThreadPool派发到后台线程,不能让主线程阻塞。懒加载能极大降低初始展示时间,用户滚动到哪,数据加载到哪,体验好很多。

4.3 异步加载:界面流畅的关键一步

如果数据源本身读取耗时(比如网络API、磁盘大文件、数据库查询),那么无论你怎么优化Model/View,主线程一旦被IO操作阻塞,界面就会卡顿。所以大数据量的常规做法是异步加载:

// 后台线程准备数据 QFutureWatcher<QVector<TableRowData>>* watcher = new QFutureWatcher<QVector<TableRowData>>(this); connect(watcher, &QFutureWatcher<TableRowData>::finished, this, [this, watcher]() { QVector<TableRowData> rows = watcher->result(); m_model->setRows(rows); watcher->deleteLater(); }); QFuture<QVector<TableRowData>> future = QtConcurrent::run([this]() { // 耗时操作:数据库查询、文件解析... return m_dataSource->fetchAllRows(); }); watcher->setFuture(future);

这里用QFutureWatcher的原因很简单:它可以通知主线程“数据准备好了”,同时还自动处理线程安全问题。重点提醒一下,不要在子线程里直接调用setRows()或者beginResetModel(),这些操作必须发生在主线程(也就是有QCoreApplication事件循环的线程),否则会造成竞态甚至崩溃。

4.4 数据对比:三种方案的效率差异

为了更直观地理解,这里给一个我实际测试的对照数据,数据量为3万行、10列,测试环境是普通办公电脑。

方案填充耗时(毫秒)滚动流畅度适用场景
QTableWidget默认逐行setItem3200+卡顿几百行场景
QTableWidget批量填充+关闭刷新680轻微卡顿几千行场景
QTableView + 自定义Model40流畅万级到十万级
QTableView + 自定义Model + 懒加载首次展示约10ms极流畅十万到百万级

注意这里的滚动流畅度是按“拖动滚动条”判断的。QTableWidget方案就算填充不卡,滚动也会因为内部全表重绘而掉帧;QTableView方案由于只绘制可见区域,滚动基本不依赖总行数。

4.5 关闭不必要的功能

在大数据量场景下,有些QTableView默认开启的功能其实很耗性能。建议根据实际情况关闭:

  • setWordWrap(false):关闭单元格内的自动换行,否则文本宽度计算和行高计算非常耗时。
  • setShowGrid(false):隐藏网格线能减少绘制工作,尤其单元格极多时。
  • setAutoScroll(false):鼠标拖拽到边缘时,禁止自动滚动,避免意外触发大范围滚动重绘。
  • setSelectionMode(QAbstractItemView::NoSelection):如果不需要选择与高亮,直接禁用选择模式。

这些改动单独看影响不大,组合起来配合setUniformRowHeights(true),对滚动性能有明显提升。

5. 常见问题与排查技巧实录

5.1 为什么换了QTableView+自定义Model还是很慢?

这是新手最容易碰到的坑,排查方向有两个。第一个,data()函数里做了耗时操作。因为视图滚动时每个可见单元格都会调用data(),如果你在data()里做数据库查询、文件IO,或者每次toString都重新格式化复杂时间,视图会反复执行这些重操作。解决方法是预先处理好耗时数据,data()里只做简单的取值与字符串拼接。

第二个,没有实现setUniformRowHeights。如果行高不一致,视图滚动时每次都要动态计算行高,行数越多越慢。若必须使用自适应行高,可以缓存行高结果。

5.2 列数很多(比如50列以上)怎么处理?

列数多会导致单元格总数爆炸,比如10万行50列就是500万个可见单元项,即使model懒加载,横向滚动也会带来压力。实际项目里,最直接的手段是减少列数,只显示用户最关心的核心字段,次要信息用详情面板展示。其次是给列设置固定宽度,避免横向滚动时反复计算。

如果列数确实无法压缩,可以在model的headerData里提前把各列标题存入QStringList,减少反复获取;data()里也要避免过于复杂的角色逻辑。

5.3 数据源是数据库,如何设计分页查询?

分页查询的关键是“拖到底部再触底加载”,可以用垂直滚动条的最大值来近似判断:

connect(tableView->verticalScrollBar(), &QScrollBar::valueChanged, this, [this](int value) { const int max = tableView->verticalScrollBar()->maximum(); if (value >= max - 20) { loadNextPage(); // 查询下一页并append到model } });

loadNextPage里注意用追加方式插入,也就是model的appendRows方法,而不是每次都重置整个model。这样视图不会丢失滚动位置,用户的体验接近“无限滚动”。同时要做好防止重复触发,比如设置一个isLoading标志位。

5.4 QTableWidget里已经写了很多业务代码,能平滑迁移吗?

能,但要有心理准备。我的建议是先不加新功能,只把QTableWidget替换成QTableView+自定义Model,保持界面UI行为一致,然后在新的代码基础上迭代。原先针对QTableWidget的item访问接口(如item(row, col)->text()),要改成model->data(model->index(row, col))的形式,这个改动量确实不小,但换来的是后续性能不再成为瓶颈。

如果暂时不想大改,先用第2节的“批量填充三件套”撑住,但心里要清楚这只是过渡,不解决根本问题。

5.5 单元格需要显示图标或富文本时怎么办?

自定义model的data()可以返回QPixmap、QIcon、QColor等类型,视图会直接绘制。不过尽量避免在data()里实时加载大图,最好在数据源阶段把图标缓存在QVariant或QPixmap对象中,data()返回时只是拷贝引用或指针,性能影响很小。

富文本如果要用Qt::RichText渲染,单个单元格会走QTextDocument绘制,非常耗时。数据显示量较大时,建议只用普通的纯文本+前景色+背景色来表示状态,不要轻易启用富文本。

6. 替代方案与补充工具

6.1 考虑过QStandardItemModel吗?

有朋友会问,不用QTableWidget,但也不想自己写模型,能不能用QStandardItemModel直接配QTableView?答案是可以,但性能和QAbstractTableModel相比仍有差距。原因在于QStandardItemModel内部每个单元格仍然是一个QStandardItem对象,虽然比QTableWidgetItem轻量得多,但无法避免逐格创建和管理的开销。我实测3万行数据,QStandardItemModel填充耗时为QAbstractTableModel的4到5倍。

如果数据量在1万行以内,QStandardItemModel可以省去自定义模型的功夫。超过这个量级,强烈建议还是写一个精简的QAbstractTableModel子类。

6.2 终极武器:QAbstractItemModel与委托组合

如果你需要极致性能并且界面交互复杂,可以将自定义model和自定义委托QStyledItemDelegate结合使用。委托的核心作用是控制“如何绘制”单元格,把那些视觉表现逻辑(比如进度条、百分比标签、状态点)从data()里剥离出来。

这样做的好处是彻底分离数据和显示。model只返回最原始的QVariant数据,委托负责把数据绘制成你想要的视觉样式。委托还可以在paint里缓存绘制结果,避免重复计算。不过委托的学习成本更高,比较适合数据量大且展示要求高的商用软件使用,如果只是内部工具,自定义model已经足够。

7. 最后的实际操作建议

我自己在做类似优化时,通常会先跑一次基准测试,把数据量和加载耗时记录下来,这样后续优化才有对比依据。很多人在网上问“为什么我的QTableWidget又卡了”,其实答案经常就藏在数据量和填充方式里。

一个非常推荐的临时救急小技巧是:加载前先调用QApplication::setOverrideCursor(Qt::WaitCursor),然后配合setUpdatesEnabled和blockSignals批量填充,至少能给用户“程序还在干活”的反馈,避免被误认为崩溃。加载完成后恢复光标,再视情况弹一个状态栏提示“加载完成,共N行”。

如果日常维护的是几百万行的超大表格,不要只依赖控件优化,还要从上层的业务逻辑优化着手。比如数据是否真的需要一次性全部载入内存,是否可以增加条件过滤、是否可以只加载最近一周的数据。表格显示只是表象,真正的性能瓶颈往往在数据产生和传递环节。

最后分享一个我一直使用的策略:把表格的“加载”、“刷新”、“查询”三个入口统一封装成接口,内部切换不同数据源时,界面层无感知,这样方便后期替换和优化。以我的经验,把这个改造完成后,后续再加数据量再大也不是慌乱了。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 12:21:59

Python+OpenCV+dlib实现人眼检测与眨眼识别

简介&#xff1a;一份面向Python与OpenCV初学者及计算机视觉开发者的完整工程包&#xff0c;聚焦实时人眼识别、眨眼检测与闭眼检测&#xff0c;提供在Ubuntu环境下的源代码、模型文件与图文教程。工程以OpenCV的Haar级联分类器实现人眼定位&#xff0c;结合人脸关键点模型辅助…

作者头像 李华
网站建设 2026/9/8 12:20:12

边缘计算视觉模型部署实战:破解延迟与断网难题

如果要在 Physical AI&#xff08;物理人工智能&#xff09;落地时只解决一个问题&#xff0c;我会选延迟&#xff1b;如果还能再解决一个&#xff0c;那就是断网。视觉模型在云端跑得好好的&#xff0c;一旦要装进 AGV 小车、巡检机器人或者工厂产线&#xff0c;网络抖动和推理…

作者头像 李华
网站建设 2026/9/8 12:18:50

AI芯片CNN加速器设计:从算法到FPGA落地全流程

做AI芯片的同行&#xff0c;尤其是从FPGA起步做CNN加速器的朋友&#xff0c;应该都有这种体会&#xff1a;看论文时觉得卷积不就是乘加嵌套循环&#xff0c;真到RTL阶段才发现带宽、时序、数据流、握手协议一堆问题冒出来。这个[AI芯片]4-1-CNN加速器设计项目&#xff0c;其实就…

作者头像 李华
网站建设 2026/9/8 12:18:38

大模型训练显存优化:混合精度与分布式训练实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/8 12:17:17

Transformers微调实战指南:从迁移学习原理到LoRA中文情感分析

做迁移学习和 Transformers 微调&#xff0c;我踩过不少坑&#xff0c;也总结出一套能直接上手的路径。这篇文章不讲虚的&#xff0c;全部是实操层面的东西&#xff1a;版本怎么选、数据怎么喂、三种微调方式怎么取舍、训练时监控什么、出了错怎么排查&#xff0c;最后再带一个…

作者头像 李华