
1. 为什么我要用本地35B模型从零搓一个五子棋AI先说结论我用一个本地部署的35B参数开源大模型通过53轮对话、大约40分钟从零写出了一套能跑、能下、能赢的五子棋AI。整个过程没有联网、没有调用任何云端API模型就跑在我自己这台带24G显存的机器上。这件事的意义不在于五子棋本身有多难而在于它验证了一条路径本地大模型完全有能力承担一个完整小项目的架构设计、代码生成、调试迭代和逻辑修正。你不需要一个庞大的团队也不需要每个月交几百块的订阅费只要有一台配置过得去的机器加上一套正确的对话策略就能把脑子里的想法变成能运行的代码。我为什么要选五子棋因为它是一个“麻雀虽小五脏俱全”的项目。它涉及棋盘数据结构、胜负判定算法、AI搜索策略、界面交互四个核心模块每个模块都有明确的输入输出逻辑闭环清晰。如果模型能把这个项目从头到尾写对说明它在状态管理、递归搜索、边界条件处理这些编程核心能力上是过关的。反过来如果它写错了我也能很快定位是哪个环节出了问题因为五子棋的规则足够简单不存在模糊地带。这篇文章适合三类人看第一类是想尝试本地模型做开发但不知道从什么项目入手的人第二类是已经在用AI辅助写代码但总觉得“差点意思”、生成质量不稳定的人第三类是对五子棋AI本身感兴趣想了解极小化极大算法和Alpha-Beta剪枝怎么落地的人。我会把53轮对话中每一轮的关键决策、踩过的坑、以及最终沉淀下来的对话模板都拆开讲清楚。2. 本地模型选型与运行环境搭建2.1 为什么是35B这个量级模型参数量的选择直接决定了三件事生成质量、推理速度、显存占用。我试过7B和13B的模型7B写简单函数没问题但一到多文件协作和递归逻辑就开始胡言乱语经常把棋盘坐标和数组索引搞混。13B好一些但在处理“判断五子连珠”这种需要遍历四个方向、每个方向检查两端边界的逻辑时仍然会漏掉角落情况。35B是一个甜点位置。它的推理能力足够理解“极小化极大搜索需要评估函数返回一个分数分数要同时考虑进攻和防守”这种抽象描述同时经过4-bit量化之后显存占用能压到20G以内单张消费级显卡就能跑。我用的是Q4_K_M量化版本实测生成速度在每秒18到25个token之间写一个50行左右的函数大概需要30秒到1分钟这个速度在对话式开发中是可以接受的。注意不要盲目追求70B。70B量化后至少需要40G显存而且推理速度会降到每秒5到8个token一轮对话等三分钟开发节奏会被彻底打乱。35B是目前本地开发场景下性价比最高的选择。2.2 运行环境的具体配置我的硬件配置是AMD Ryzen 9 5900X、64G DDR4内存、RTX 3090 24G显存、2TB NVMe固态。这个配置不算顶级但足够流畅运行35B的量化模型。如果你用的是RTX 4090或者A6000体验会更好但3090已经完全够用。软件层面我用的是LM Studio来加载和管理模型。选它的原因很简单图形界面友好支持GPU层数调节能实时看到显存占用和生成速度而且内置了OpenAI兼容的API服务端方便后续用脚本批量调用。加载模型时的关键参数设置如下GPU Offload设置为35层总共41层剩下的6层放在CPU上跑。这样显存占用大约19G留出5G给系统和IDE。Context Length设置为8192。五子棋项目的代码总量大约在1500行左右加上对话历史8192的上下文窗口刚好够用。再大就会挤占显存。Temperature设置为0.2。代码生成需要确定性温度太高会导致同一个问题每次生成的代码风格不一致增加调试难度。Top P设置为0.9配合低温度使用保证生成结果的多样性不会完全消失。提示如果你的显存只有16G可以把GPU Offload降到28层速度会慢一些但能跑起来。如果显存只有12G建议换13B模型强行跑35B会频繁触发内存交换体验极差。2.3 对话工具的选择与配置我直接用LM Studio内置的聊天界面进行对话没有用Cursor或者VS Code插件。原因有两个第一LM Studio的聊天界面支持Markdown渲染和代码块高亮阅读模型生成的代码很舒服第二我需要频繁地复制代码到本地文件测试LM Studio的复制按钮就在代码块右上角操作路径最短。如果你习惯在IDE里工作也可以用Continue或者Cursor的本地模型模式把LM Studio的API地址配置进去。但要注意Cursor的本地模型支持在某些版本里不稳定容易出现“workbuddy调用本地模型报错”这类问题排查起来很麻烦。我的建议是架构设计和核心算法生成在LM Studio里做代码补全和语法检查在IDE里做各取所长。3. 53轮对话的完整流程拆解3.1 第一阶段项目骨架与数据结构定义第1-8轮第一轮对话我没有直接让模型写代码而是先让它帮我梳理项目结构。我的原话是“我要用Python写一个五子棋AI包含棋盘表示、胜负判定、AI搜索、命令行界面四个模块。请给出每个模块的职责和它们之间的调用关系。”模型返回了一个清晰的模块划分board.py负责棋盘状态管理judge.py负责胜负判定ai.py负责搜索算法main.py负责游戏循环。这个划分比我预想的更合理因为它把胜负判定单独拆出来了避免了棋盘类过于臃肿。第二轮我让模型定义棋盘的数据结构。它给出了一个15x15的二维列表用0表示空位、1表示黑子、2表示白子。这个方案中规中矩但我追问了一句“如果用一维数组表示索引计算会不会更快”模型回答“一维数组在缓存局部性上确实有优势但二维列表的可读性更好对于15x15的规模性能差异可以忽略。”这个回答让我决定采用二维列表因为后续调试时打印棋盘更直观。第三轮到第五轮我让模型分别实现了棋盘的初始化、落子、撤销落子、打印棋盘四个方法。这里出现了一个小问题模型在打印棋盘时用了Unicode的圆圈符号但在某些终端下显示为乱码。我让它改成用“X”和“O”表示黑白子问题解决。第六轮到第八轮我让模型写胜负判定的逻辑。这是第一个关键点。模型最初给出的代码只检查了水平方向我指出后它补上了垂直和两个对角线方向。但我测试时发现它在检查对角线时数组越界了。我把报错信息贴给它它立刻意识到是边界条件写错了修正后的代码如下def check_win(board, row, col, player): directions [(0,1), (1,0), (1,1), (1,-1)] for dr, dc in directions: count 1 # 正向检查 r, c row dr, col dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r dr c dc # 反向检查 r, c row - dr, col - dc while 0 r 15 and 0 c 15 and board[r][c] player: count 1 r - dr c - dc if count 5: return True return False这段代码的关键在于正反两个方向都要检查而且边界判断要放在数组访问之前。模型第一次写的时候把board[r][c]写在了边界判断前面导致越界。这个错误很典型值得记下来。3.2 第二阶段AI搜索算法的实现与优化第9-25轮第九轮我开始让模型实现AI的搜索算法。我给的描述是“用极小化极大算法搜索深度设为3层评估函数要考虑活四、冲四、活三、眠三、活二这几种棋型的分数。”模型第一次生成的代码结构是对的但评估函数写得太粗糙只给了活四10000分、活三1000分没有区分冲四和眠三。我让它细化评分表它给出了一个更完整的版本棋型黑方得分白方得分说明活四100000100000两端开放的四连冲四1000010000一端被封的四连活三80008000两端开放的三连眠三500500一端被封的三连活二300300两端开放的二连眠二5050一端被封的二连这个评分表的逻辑是活四必胜冲四和活三需要立即应对活二和眠二属于布局阶段。但模型在实现时犯了一个错误它把黑方和白方的得分写成了对称的实际上在评估函数中应该用“我方得分减去对方得分”来体现攻防兼备。我指出这个问题后模型修正为def evaluate(board, player): my_score calculate_score(board, player) opp_score calculate_score(board, 3 - player) return my_score - opp_score * 1.2乘以1.2是一个经验系数意思是防守的权重略高于进攻。这个系数是我让模型加的因为在实际对局中漏掉对方的活三比自己的活三被堵更致命。第十轮到第十五轮我让模型实现Alpha-Beta剪枝。这是整个项目最复杂的部分。模型第一次生成的代码没有正确传递alpha和beta值导致剪枝完全失效。我把搜索树的节点访问次数打印出来发现和没有剪枝时一样多。我把这个现象描述给模型它检查后发现问题出在递归调用时参数顺序写反了。修正后的核心代码如下def alpha_beta(board, depth, alpha, beta, maximizing, player): if depth 0 or check_game_over(board): return evaluate(board, player) if maximizing: max_eval float(-inf) for move in get_valid_moves(board): board[move[0]][move[1]] player eval_score alpha_beta(board, depth-1, alpha, beta, False, player) board[move[0]][move[1]] 0 max_eval max(max_eval, eval_score) alpha max(alpha, eval_score) if beta alpha: break return max_eval else: min_eval float(inf) for move in get_valid_moves(board): board[move[0]][move[1]] 3 - player eval_score alpha_beta(board, depth-1, alpha, beta, True, player) board[move[0]][move[1]] 0 min_eval min(min_eval, eval_score) beta min(beta, eval_score) if beta alpha: break return min_eval这段代码的关键在于alpha和beta的更新方向在最大化层更新alpha在最小化层更新beta而且剪枝条件beta alpha要放在更新之后。模型第一次把更新和剪枝的顺序搞反了导致剪枝逻辑混乱。第十六轮到第二十轮我让模型优化搜索效率。它提出了三个优化点第一只考虑已有棋子周围两格内的空位而不是遍历整个棋盘第二对候选走法按评估分数排序优先搜索高分走法第三用置换表缓存已经搜索过的局面。前两个优化模型直接实现了第三个我让它先跳过因为置换表的哈希计算容易出bug而且对于深度3的搜索收益不明显。第二十一轮到第二十五轮我让模型写了一个简单的测试脚本让AI自己和自己下棋验证搜索逻辑的正确性。这里出现了一个有趣的问题AI在第一步总是下在天元位置但第二步开始就乱下了。我检查后发现评估函数在棋盘为空时返回0导致所有走法的分数一样AI就随便选了一个。我让模型在评估函数里加了一个“中心偏好”的加分项距离中心越近分数越高问题解决。3.3 第三阶段界面交互与游戏循环第26-40轮第二十六轮开始做命令行界面。我让模型写一个简单的文本界面用input()获取玩家落子坐标然后调用AI计算并落子。模型很快就写出来了但有一个体验问题每次落子后重新打印整个棋盘屏幕会闪烁。我让它改成用os.system(cls)清屏后再打印闪烁问题解决。第二十八轮到第三十二轮我让模型加入难度选择功能。简单模式搜索深度1层中等模式3层困难模式5层。这里遇到一个性能问题5层搜索在Python里跑一次要十几秒玩家等不了。我让模型加入迭代加深和时间限制如果搜索超过5秒就返回当前最优解。模型实现了这个逻辑但第一次写的时候把时间检查放在了递归函数内部导致每个节点都要调用一次time.time()反而更慢了。修正后改成每搜索完一层检查一次时间性能恢复正常。第三十三轮到第三十七轮我让模型加入悔棋功能。这个功能需要保存历史棋盘状态。模型最初用列表存储每个历史棋盘的深拷贝内存占用很大。我让它改成只存储落子记录悔棋时反向撤销内存占用从几十兆降到了几KB。第三十八轮到第四十轮我让模型写一个简单的AI对战AI的观战模式。这个模式不需要玩家输入两个AI轮流落子每步间隔0.5秒方便观察AI的棋路。模型很快实现了但在观战模式下发现AI有时候会下在已经有棋子的位置。我检查后发现是get_valid_moves函数在棋盘快满时返回了非法位置修正了边界判断后问题解决。3.4 第四阶段调试、优化与最终验证第41-53轮第四十一轮到第四十五轮我让模型对整个项目做代码审查。它指出了三个问题第一judge.py里的胜负判定在棋盘边缘有重复计算第二ai.py里的评估函数每次都要遍历整个棋盘效率低第三main.py里的异常处理不完整玩家输入非数字会崩溃。我让模型逐一修复它给出了具体的修改方案。第四十六轮到第四十九轮我让模型写单元测试。它用unittest框架写了12个测试用例覆盖了棋盘初始化、落子、胜负判定、AI搜索等核心功能。运行后发现有两个测试失败了一个是AI在必胜局面下没有选择获胜走法另一个是悔棋后棋盘状态不一致。我把失败信息贴给模型它分别修正了评估函数中的必胜判断和悔棋时的状态恢复逻辑。第五十轮到第五十三轮我让模型做最终的代码整理和注释补充。它把所有的魔法数字提取成了常量给每个函数加了docstring还写了一个README说明如何运行。最后我手动跑了一遍完整对局从开局到AI获胜全程没有报错AI的棋力在中等难度下能稳定击败我这个业余玩家。4. 本地模型做AI Coding的实战经验与避坑指南4.1 对话策略怎么问才能让模型写出能跑的代码53轮对话下来我最大的体会是提问的粒度决定了生成代码的质量。如果你说“帮我写一个五子棋AI”模型会给你一个能跑但棋力极弱的版本而且代码结构混乱。如果你说“用极小化极大算法实现搜索深度为3的AI评估函数包含活四、冲四、活三、眠三、活二五种棋型返回最佳落子坐标”模型生成的代码直接就能用。我总结了一个提问模板适用于大多数算法类代码生成上下文当前项目已有[某模块]数据结构是[某结构]。 任务请实现[某功能]输入是[某参数]输出是[某结果]。 约束要求[某性能指标]不能使用[某库]边界情况包括[某情况]。 示例输入[某示例]期望输出[某结果]。这个模板的核心是把模型的自由发挥空间压缩到最小。本地35B模型不是GPT-4它的推理能力有限你给它的约束越明确它犯错的概率越低。4.2 常见问题与排查技巧在53轮对话中我遇到了十几类问题下面挑最有代表性的几个整理成速查表问题现象根本原因解决方法模型生成的代码数组越界边界判断写在数组访问之后把边界检查提到数组访问之前或者用try-except包裹Alpha-Beta剪枝不生效alpha/beta更新顺序错误确保最大化层更新alpha、最小化层更新beta剪枝条件放在更新之后AI在必胜局面下不进攻评估函数没有区分胜负和普通棋型在评估函数最前面加判断如果已连五返回极大值如果对方连五返回极小值搜索速度太慢遍历了整个棋盘的空位只搜索已有棋子周围2格内的空位按评估分数排序候选走法模型忘记之前的代码约定上下文窗口被长代码挤满每10轮对话后把核心数据结构和函数签名复制到新的对话中生成的代码风格不一致温度参数太高把Temperature降到0.2以下并在对话开头明确代码规范提示如果模型反复在同一个问题上犯错不要一直让它重写。把正确的代码片段贴给它说“请参考这个写法重新实现某函数”这样比让它从零生成更高效。4.3 本地模型做AI Coding的边界在哪里53轮对话让我看清了本地35B模型的能力边界。它能做好的事情包括根据明确的接口定义生成函数实现、根据报错信息定位常见bug、根据自然语言描述生成单元测试、对已有代码做格式整理和注释补充。这些事情的特点是逻辑闭环清晰、输入输出明确、不需要跨领域的知识融合。它做不好的事情包括设计复杂的系统架构、处理模糊的需求描述、在多个模块之间做权衡取舍、生成需要深度领域知识的代码。比如我让它设计一个“支持联网对战”的五子棋架构它给出的方案非常粗糙没有考虑网络延迟、状态同步、断线重连这些实际问题。所以我的建议是把本地模型当成一个执行力很强但经验不足的初级工程师。你负责架构设计和关键决策它负责把决策翻译成代码。你负责测试和验证它负责根据反馈快速修改。这个分工下本地模型的生产力提升是实实在在的。5. 五子棋AI核心算法的深度解析5.1 极小化极大算法的直觉理解极小化极大算法的核心思想可以用一句话概括我走一步假设对手会走最不利于我的一步然后我在这两个前提下选择最有利于我的一步。这就像下棋时你不仅要考虑自己怎么走还要考虑对手会怎么回应。在代码实现中这个思想体现为两层递归一层是“最大化层”代表AI自己走棋选择评估分数最高的走法另一层是“最小化层”代表对手走棋选择评估分数最低的走法。两层交替递归直到达到搜索深度或者游戏结束。搜索深度为3的意思是AI走一步、对手走一步、AI再走一步然后评估局面。深度越深AI的棋力越强但计算量也指数级增长。15x15的棋盘有225个空位深度3的搜索树节点数大约是225的三次方超过一千万个节点。这就是为什么必须用Alpha-Beta剪枝和候选走法筛选来降低计算量。5.2 评估函数的设计哲学评估函数是五子棋AI的灵魂。它决定了AI认为什么样的局面是“好”的。一个设计良好的评估函数应该同时考虑进攻和防守而且要对不同的棋型给予合理的分数差距。我采用的评分体系是活四100000分、冲四10000分、活三8000分、眠三500分、活二300分、眠二50分。这个分数梯度的逻辑是活四和冲四之间差了10倍因为活四必胜而冲四可以被堵冲四和活三之间差了1.25倍因为冲四的威胁更紧迫活三和眠三之间差了16倍因为活三可以发展成活四而眠三不能。在实际对局中这个评分体系让AI表现出了一定的“棋感”它会优先堵对方的活三同时尝试构建自己的活三。但它也有局限性比如在开局阶段由于棋盘上棋子很少评估函数的分数差异不明显AI的走法看起来比较随机。这是极小化极大算法的通病需要通过开局库或者中心偏好来弥补。5.3 性能优化的三个关键手段第一个手段是候选走法筛选。不遍历整个棋盘只考虑已有棋子周围2格内的空位。在开局阶段这个优化能把候选走法从225个降到20个左右搜索树节点数直接降了一个数量级。第二个手段是走法排序。在递归之前先用评估函数给每个候选走法打分按分数从高到低排序。这样Alpha-Beta剪枝能更早地触发因为高分走法更有可能产生剪枝。实测下来走法排序能让搜索速度提升3到5倍。第三个手段是迭代加深。先搜索深度1层得到最佳走法再搜索深度2层验证深度1的结果以此类推直到达到目标深度或者时间限制。迭代加深的好处是如果时间不够至少有一个深度较浅的可用结果而且每一层的搜索结果可以用于下一层的走法排序。6. 从五子棋项目延伸出去的思考这个项目做完之后我一直在想一个问题本地模型做AI Coding的瓶颈到底在哪里。53轮对话中模型生成的代码大约有70%是一次通过的20%需要一轮修改10%需要两轮以上的反复调试。这个比例在简单项目上是可以接受的但如果项目规模扩大到几千行代码反复调试的成本就会变得很高。我的判断是瓶颈不在模型的生成能力而在上下文管理。当对话轮数超过30轮模型对早期约定的记忆开始模糊它会忘记之前定义的数据结构、函数命名规范、错误处理方式。我试过在对话中反复强调这些约定但效果有限因为上下文窗口是有限的强调的内容多了留给代码生成的空间就少了。一个可行的解决方案是分阶段重置上下文。每完成一个模块就把该模块的最终代码和接口定义整理成一个摘要作为下一阶段对话的起点。这样既能保持约定的连续性又能释放上下文空间。我在第40轮左右用了这个方法把棋盘和胜负判定的代码摘要贴给模型然后开始做AI搜索的优化效果比一直在一个长对话里改要好得多。另一个思考是本地模型和云端模型的配合。本地模型适合做代码生成和快速迭代云端模型适合做架构评审和复杂bug分析。我现在的做法是本地模型写代码遇到搞不定的问题把相关代码和报错信息整理好发给云端模型分析原因拿到解决方案后再让本地模型执行修改。这个混合模式兼顾了隐私、成本和效率。最后说一个具体的技巧让模型写测试比让模型写代码更有价值。在第46轮到第49轮我让模型写了12个单元测试这些测试不仅帮我发现了两个隐藏的bug还成了后续修改代码时的回归验证工具。每次模型修改了核心逻辑我就跑一遍测试确保没有引入新的问题。这个习惯我建议每个用AI写代码的人都养成因为AI生成的代码最大的风险不是“写错了”而是“改坏了”——你让它修一个bug它可能引入两个新bug有了测试就能第一时间发现。