作为一个写了20年代码的老头子,我是怎么在本地跑起大模型的

说出来你们可能不信,我现在写代码已经离不开AI了。

不是那种科幻片里机器人要统治人类的 剧情啊,就是实打实的–每天写业务代码的时候旁边开着个本地大模型,有事没事问它两嘴,比google搜文档快多了。你们想想,年纪大了,记性不如从前了,什么C#的新特性啦、.NET的新API啦,有时候真记不全乎。

但是用云端的API吧说实话有几个问题:一是得联网,二是钱花得心疼(量大是真的贵),三是有些代码涉及内部业务,不太方便传出去。于是我就寻思–能不能在本地跑个大模型?

答案是:能!而且比我想象的简单多了。

这篇文章就是记录一下我的心路历程,从装环境到跑通第一个Hello World,保姆级教程,手把手带你们入坑。


先把Ollama装起来

我估计很多兄弟跟我一样,听到"本地大模型"第一反应是头皮发麻–这得买多贵的显卡啊?

其实吧,没那么夸张。现在开源模型做得越来越轻量了,普通的游戏显卡甚至纯CPU都能跑。我用的是Ollama这个工具,怎么说呢,它就是把"跑大模型"这件事做得像安装一个软件那么简单。

Windows安装(我是主力机)

在Windows上安装简直不能更简单:

  1. 去官网 https://ollama.com/download 下载安装程序
  2. 双击运行,等它安装完
  3. 就这么完了?

对就这么完了。安装程序会自动帮你配置环境变量,创建系统服务。你甚至不需要手动去做什么配置。

安装完成后,你可以在命令行试试:

ollama --version

能看到版本号就说明装好了。第一次运行的时候它会自己启动服务,默认监听11434端口。

Linux服务器安装

我另外有一台Linux服务器,有时候也在那边跑。安装方式也很简单:

curl -fsSL https://ollama.com/install.sh | sh

这个脚本会自动检测你的系统,下载对应的二进制文件,还会帮你配置systemd服务。装完以后可以用这些命令管理:

# 看看服务状态
systemctl status ollama

# 开机自启
systemctl enable ollama

# 重启服务
systemctl restart ollama

顺便说一句,如果你用的是Docker,官方也有镜像,一行命令就搞定:

docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

我个人推荐直接装原生版本,性能会好一些,GPU加速也更容易发挥出来。

装完以后的配置

Ollama装好以后默认就能用,但有几个地方可以调教一下:

默认端口是11434,绑定地址是127.0.0.1。如果你想要局域网其他机器也能访问,可以改环境变量:

# Linux下
export OLLAMA_HOST=0.0.0.0:11434

# Windows下
set OLLAMA_HOST=0.0.0.0:11434

模型存在哪里?默认是用户目录下的.ollama/models。如果你的C盘空间紧张,可以改:

# 换个存放位置
export OLLAMA_MODELS=/data/ollama/models

选个趁手的模型

环境装好了,接下来就是挑模型。

你们可能问了,模型不都差不多吗?选一个下不就完了?

那可不对。选错模型的下场就是–要么跑不动(显存不够),要么回答质量太差(问它C#问题它给你扯Java),要么速度慢得让你怀疑人生。

几个好用的模型

日常开发首选:Qwen3:8b

没错,就是阿里巴巴达摩院开发的通义千问系列模型。实测下来,这个模型对C#的支持相当到位。你问它 async/await 怎么用,它能给你讲得明明白白;你让它帮你写个斐波那契数列,它能给你写出好几种实现方式。

关键是这个大小–8B参数,Q4_K_M量化后约4.9GB。我那台16GB内存的电脑跑起来毫无压力,RTX 3060显卡加持下每秒能出几十个token,速度感人。

复杂问题求解:DeepSeek-R1:8b

这个是推理型模型,特别适合处理那种"挠破头也想不出来"的问题。比如算法题啊、架构设计啊,扔给它准没错。

它有个特点–会先"思考"一下再回答你,把推理过程也展示出来。有时候看它是怎么分析的,比直接得到答案还有收获。

轻量备用:Llama 3.2:3b

如果你显卡都没有,就是纯CPU跑,那3B参数的Llama是个好选择。2GB左右,占用极低,写个简单的代码补全、问个技术概念完全够用。

量化是个啥?

可能有人要问了–什么是Q4_K_M?

这就涉及到一个很重要的概念:模型量化。

你们知道模型训练完以后,权重文件通常是 FP16(16位浮点数)甚至 FP32(32位浮点数)的。这精度是高,但太大了,一张RTX 4090也就能跑70B参数的模型。于是就有人研究怎么把精度降下来–从16位压到8位、4位,这就是"量化"。

量化级别从高到低大致是:F16(无损)> Q8_0 > Q6_K > Q5_K_M > Q4_K_M > Q4_0

Q4_K_M 是社区推荐最多的平衡方案–体积只有原来的四分之一左右,质量损失很小,性价比之王。我上面说的模型大小默认都是Q4_K_M量化后的结果。

顺便提一句,量化级别不仅影响模型大小,也会影响推理速度。量化位数越低,计算越快,但质量损失也越大。所以 Q4_K_M 在速度和质量之间取得了很好的平衡,这也是它成为社区推荐的原因。如果你追求极致速度且不在乎质量略降,可以试试 Q4_0;如果你显存充裕想要更高质量的回答,可以用 Q8_0 甚至 F16。

怎么拉模型?简单:

# 拉取默认版本(Ollama会自动选Q4_K_M)
ollama pull qwen3:8b

# 查看已下载的模型
ollama list

# 删除不用的模型
ollama rm llama3.2:3b

C#怎么跟Ollama对话

好了,环境装好了,模型也下好了。现在的问题是–怎么在C#里调用它?

方案一:OllamaSharp(强烈推荐)

这是我最推荐的方式。OllamaSharp是一个社区开发的SDK,微软官方都在用–Semantic Kernel、.NET Aspire都用它。质量绝对有保障。

先装包:

dotnet add package OllamaSharp

然后就可以开始写了。我先给你们看个最简单的例子:

using OllamaSharp;

// 初始化客户端,指向本地Ollama服务
var ollama = new OllamaApiClient(new Uri("http://localhost:11434"));
ollama.SelectedModel = "qwen3:8b";

// 问它一个问题
Console.WriteLine("请问:C#中async和await是什么?");
Console.Write("AI回答:");

await foreach (var stream in ollama.GenerateAsync("请用通俗易懂的语言解释C#中的async和await关键字"))
{
    Console.Write(stream.Response);
}

看到没,这就是流式输出–AI回答的时候是一个字一个字蹦出来的,跟真人在打字一样。这个体验,比等它全部生成完再显示要好太多了。

方案二:自己写HttpClient

有的兄弟可能不喜欢依赖第三方包,那也没问题,Ollama本身提供的是标准的REST API,咱们自己写个HttpClient也能调。

using System.Net.Http.Json;
using System.Text.Json;

public class OllamaClient
{
    private readonly HttpClient _http;
    private const string BaseUrl = "http://localhost:11434";

    public OllamaClient()
    {
        _http = new HttpClient
        {
            BaseAddress = new Uri(BaseUrl),
            Timeout = TimeSpan.FromMinutes(5)
        };
    }

    // 列出所有已下载的模型
    public async Task<List<ModelInfo>> ListModelsAsync()
    {
        var response = await _http.GetFromJsonAsync<JsonDocument>("/api/tags");
        var models = new List<ModelInfo>();

        foreach (var model in response!.RootElement.GetProperty("models").EnumerateArray())
        {
            models.Add(new ModelInfo(
                model.GetProperty("name").GetString()!,
                model.GetProperty("size").GetInt64(),
                model.GetProperty("modified_at").GetDateTime()
            ));
        }
        return models;
    }

    // 非流式生成(一次性返回)
    public async Task<string> GenerateAsync(string model, string prompt)
    {
        var request = new
        {
            model,
            prompt,
            stream = false,  // 关闭流式
            options = new { temperature = 0.7 }
        };

        var response = await _http.PostAsJsonAsync("/api/generate", request);
        var json = await response.Content.ReadFromJsonAsync<JsonDocument>();
        return json!.RootElement.GetProperty("response").GetString()!;
    }

    // 流式生成
    public async IAsyncEnumerable<string> GenerateStreamAsync(string model, string prompt)
    {
        var request = new { model, prompt, stream = true };

        var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/generate")
        {
            Content = JsonContent.Create(request)
        };

        using var response = await _http.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead);
        using var stream = await response.Content.ReadAsStreamAsync();
        using var reader = new StreamReader(stream);

        while (!reader.EndOfStream)
        {
            var line = await reader.ReadLineAsync();
            if (string.IsNullOrEmpty(line)) continue;

            using var json = JsonDocument.Parse(line);
            var text = json.RootElement.GetProperty("response").GetString() ?? "";
            yield return text;

            // 检查是否已经完成
            if (json.RootElement.GetProperty("done").GetBoolean())
                break;
        }
    }
}

public record ModelInfo(string Name, long Size, DateTime ModifiedAt);

用起来是这样的:

var client = new OllamaClient();

// 看看本地有哪些模型
var models = await client.ListModelsAsync();
Console.WriteLine("本地模型列表:");
foreach (var m in models)
    Console.WriteLine($"  - {m.Name} ({m.Size / 1024 / 1024 / 1024:F1} GB)");

// 流式调用
Console.WriteLine("\n流式回答示例:");
await foreach (var chunk in client.GenerateStreamAsync("qwen3:8b", "用C#写一个快速排序算法"))
{
    Console.Write(chunk);
}

代码写起来

光说不练假把式。我来带你们写一个完整的、能在实际项目里用的例子。

场景:做个本地代码助手

我写了一个简单的控制台程序,你可以问它C#相关的问题,它会实时显示回答。这个Demo包含了流式输出、错误处理、退出机制,是最基础的"可用"版本。

using System.Text;
using OllamaSharp;

Console.WriteLine("=== 本地 C# 代码助手 ===");
Console.WriteLine("输入你的问题,按回车发送。输入 exit 退出。\n");

// 初始化 Ollama 客户端
var ollama = new OllamaApiClient(new Uri("http://localhost:11434"));
ollama.SelectedModel = "qwen3:8b";

// 设置系统提示,让AI扮演C#专家
var chat = new Chat(ollama);
chat.SystemPrompt = @"你是一个资深的C#开发者,有20年的编程经验。
你的特点是:
1. 代码示例简洁、易懂、符合现代C#编码规范
2. 喜欢用async/await和多线程
3. 解释问题时喜欢用生活中的例子
4. 会指出潜在的性能问题和最佳实践";

while (true)
{
    Console.Write("\n你: ");
    var userInput = Console.ReadLine();

    if (string.IsNullOrWhiteSpace(userInput))
        continue;

    if (userInput.ToLower() == "exit")
    {
        Console.WriteLine("再见!");
        break;
    }

    Console.Write("\nAI: ");
    var fullResponse = new StringBuilder();

    try
    {
        // 流式输出
        await foreach (var response in chat.SendAsync(userInput))
        {
            Console.Write(response);
            fullResponse.Append(response);
        }

        Console.WriteLine();  // 换行
    }
    catch (Exception ex)
    {
        Console.WriteLine($"\n出错了: {ex.Message}");
    }
}

这个程序跑起来大概是这样的:

=== 本地 C# 代码助手 ===
输入你的问题,按回车发送。输入 exit 退出。

你: 什么是async/await?

AI: 哈,这个问题问得好!让我用生活中...

再来一个:Web API 版本

有兄弟可能要做成Web服务给别人用。我写了一个ASP.NET Core的示例,支持流式输出(SSE)。注意:此示例需要 .NET 8 或更高版本:

using Microsoft.AspNetCore.Mvc;
using OllamaSharp;

var builder = WebApplication.CreateBuilder(args);

// 注册Ollama服务
builder.Services.AddSingleton(new OllamaApiClient(
    new Uri(builder.Configuration["Ollama:Url"] ?? "http://localhost:11434"))
{
    SelectedModel = builder.Configuration["Ollama:Model"] ?? "qwen3:8b"
});

var app = builder.Build();

app.MapPost("/api/chat", async (OllamaApiClient ollama, [FromBody] ChatRequest request) =>
{
    var results = ollama.GenerateAsync(request.Prompt);

    return Results.Stream(
        stream: async yield =>
        {
            await foreach (var response in results)
            {
                var json = System.Text.Json.JsonSerializer.Serialize(new { content = response.Response });
                await yield.WriteAsync($"data: {json}\n\n");
            }
            await yield.WriteAsync("data: [DONE]\n\n");
        },
        contentType: "text/event-stream",
        cancellationToken: default
    );
});

app.Run();

public record ChatRequest(string Prompt);

启动服务:

dotnet run

然后测试:

curl -X POST http://localhost:5000/api/chat \
  -H "Content-Type: application/json" \
  -d '{"Prompt":"用C#写一个单例模式"}'

你会看到回答像打字一样一行行出来。


踩过的坑

作为一个老程序员,什么坑没踩过。跟你们念叨念叨,省得你们再走弯路。

坑一:模型太大,显存不够

第一次我脑子一热,直接拉了个70B的模型。嗯,显存直接爆炸,加载都加载不进来。

后来学乖了–7B-8B是消费级显卡的安全范围,14B需要24GB显存,32B往上就得等专业卡了。不确定的时候先试试小的,不够再说。

坑二:模型加载慢

有时候启动模型要等十几秒甚至几十秒。这是因为模型要从硬盘加载到内存/GPU显存。

解决方案:Ollama有个keep_alive参数,控制模型在内存中保留多久。默认是5分钟。用完想立即释放:

// 直接调用HTTP API的方式(非OllamaSharp SDK)
var request = new { model = "qwen3:8b", keep_alive = "0m" };
var response = await httpClient.PostAsJsonAsync("http://localhost:11434/api/generate", request);

或者直接命令行:

ollama stop qwen3:8b

坑三:中文回答里有乱码

这个问题一般出现在Windows上,有时候流式输出的中文会显示成乱码。大概率是控制台编码问题。

解决方法:在Program.cs最开头加上:

Console.OutputEncoding = System.Text.Encoding.UTF8;

坑四:多线程并发

如果你的应用会有多个请求同时过来,要注意Ollama对并发的处理–它会用队列串行处理同一个模型的请求。

如果真的需要高并发,要么起多个Ollama实例(不同端口),要么加个信号量限制并发数:

public class OllamaSafeService
{
    private readonly OllamaApiClient _ollama;
    private static readonly SemaphoreSlim _semaphore = new(1, 1);

    public OllamaSafeService(OllamaApiClient ollama)
    {
        _ollama = ollama;
    }

    public async IAsyncEnumerable<string> GenerateSafeAsync(string prompt)
    {
        await _semaphore.WaitAsync();
        try
        {
            await foreach (var chunk in _ollama.GenerateAsync(prompt))
                yield return chunk.Response;
        }
        finally
        {
            _semaphore.Release();
        }
    }
}

坑五:GPU没被用上

有的兄弟反映跑起来好慢,一问–他在用CPU跑,显卡白买了。

检查一下:

ollama ps

看Processor那一列,如果有GPU相关的信息说明用上了。如果全是CPU…检查一下驱动装好了没,NVIDIA驱动版本要在550以上。

坑六:请求超时

大模型生成内容是需要时间的,特别是CPU模式,可能要等好几分钟。如果你用的是默认的HttpClient超时时间,可能会遇到请求被断开的情况。

我之前就遇到过,正在等它生成一段代码,结果突然报错了。一查–超时了。

解决方法很简单,把Timeout调大:

var httpClient = new HttpClient
{
    BaseAddress = new Uri("http://localhost:11434"),
    Timeout = TimeSpan.FromMinutes(10)  // 大模型生成可能很慢
};

坑七:上下文窗口

有些问题需要很长的上下文,比如让AI分析一段很长的代码或者文档。如果你直接问,它可能会"忘"了后面说的内容。

这是因为模型有一个上下文窗口大小的限制。默认通常是4096个token(小显存设备),不过Ollama会根据你的显存大小自动调整——显存越大,默认上下文窗口也越大。你可以理解为AI的"短期记忆"就这么多,超出部分会被"遗忘"。

解决方法:可以手动设置更大的上下文窗口。

在请求中指定:

// 在请求中指定
var options = new RequestOptions
{
    Options = new Options
    {
        NumCtx = 8192  // 扩大上下文到8K
    }
};
await foreach (var stream in ollama.GenerateAsync(prompt, options))
{
    // ...
}

或者通过环境变量全局设置:

set OLLAMA_CONTEXT_LENGTH=8192  # Windows
export OLLAMA_CONTEXT_LENGTH=8192  # Linux

但要注意,上下文窗口越大,显存占用越高。如果你的显卡不够大,别硬撑。

坑八:网络访问不了

这个问题主要出现在国内–Ollama默认会尝试访问一些云端资源,在国内网络环境下可能会卡住。

解决方法:禁用云端功能。

注意:OLLAMA_NO_CLOUD 环境变量和 server.json 中的 disable_ollama_cloud 配置已在 Ollama 官方 FAQ 中正式记录。禁用云端功能后,将无法使用 Ollama 的云端模型和 Web 搜索功能。

方式一:环境变量

set OLLAMA_NO_CLOUD=1  # Windows
export OLLAMA_NO_CLOUD=1  # Linux

方式二:配置文件。在 ~/.ollama/server.json 中添加:

{
  "disable_ollama_cloud": true
}

坑九:选错模型回答质量差

有时候你问AI一个问题,它给的回答牛头不对马嘴。先别急着骂AI,先看看是不是模型选错了。

比如你用的是Llama,问它C#的问题–Llama对中文的支持本身就一般,对C#库的了解也不如阿里系的模型。我测试下来,Qwen系列在中文代码这块确实做得更好。

再比如你问的是算法题,结果用了一个"对话型"模型,效果不如"推理型"模型。DeepSeek-R1就是专门做推理的,做数学题、逻辑题特别在行。

所以我的经验是:不同场景用不同的模型,别一个模型打天下。

坑十:模型卸载不掉

有时候你想换模型,结果旧的怎么都停不掉。或者是重启Ollama服务以后,模型还在内存里占着地方。

试试这些命令:

# 强制停止模型
ollama stop model-name

# 如果停不掉,重启整个服务
systemctl restart ollama  # Linux
# 或者 Windows 服务管理器里重启 Ollama

# 最暴力的一招:kill掉进程再重启
taskkill /F /IM ollama.exe  # Windows
pkill ollama  # Linux

我一般不建议用kill这种方法,容易造成数据损坏。但如果真的卡死了,该用还是得用。


日常使用的一些小技巧

用了这么久,跟你们分享几个我日常在用的小技巧。

怎么判断模型有没有在跑

有时候不确定模型是在运行还是已经卸载了,可以直接敲:

ollama ps

这个命令会列出当前加载的模型。如果你想看本地有哪些可用模型:

ollama list

怎么知道模型详细信息

想看某个模型的具体参数?可以:

ollama show qwen3:8b

这个会告诉你模型的参数大小、量化版本、上下文长度等信息。

怎么快速切换模型

如果你有多个模型,想快速切换试试效果,可以这样做:

# 停止当前模型
ollama stop qwen3:8b

# 启动另一个模型
ollama run deepseek-r1:8b

本地模型也能有"记忆"

你们可能注意到了,OllamaSharp里的Chat类会自动记录对话历史。这意味着你可以这样连续对话:

var chat = new Chat(ollama);
chat.SystemPrompt = "你是一个C#专家";

// 第一轮
await foreach (var r in chat.SendAsync("什么是泛型?"))
    Console.Write(r);

// 第二轮 - AI还记得上一轮的内容
await foreach (var r in chat.SendAsync("那 covariance 呢?"))
    Console.Write(r);

这就是多轮对话,AI知道你在讨论什么。

调整生成参数

大模型生成内容的行为可以通过参数调整。这里说几个最常用的:

temperature(温度):控制随机性。0到2之间,越低越"保守",越高越"有创意"。一般代码生成用0.2到0.5,对话用0.7到1.0。

var options = new RequestOptions
{
    Options = new Options
    {
        Temperature = 0.3  // 更确定性的输出
    }
};

top_p:另一种控制随机性的方式,通常和temperature二选一。我一般习惯用temperature。

num_predict(最大生成长度):限制生成多少token,防止它废话太多。

让AI只输出代码

有时候你不需要AI解释那么多,只想要它生成的代码。可以设置:

chat.SystemPrompt = "你是一个C#开发者。直接给出代码,不要解释。如果需要解释,用注释写在代码里。";

或者用JSON格式输出,让它返回结构化的内容:

# Ollama支持JSON输出模式
ollama run qwen3:8b "用JSON格式返回一个用户对象,包含Name,Age,Email字段"

性能优化的经验之谈

跑了这么久,在性能这块也积累了一些经验。

GPU才是本体

我必须强调一点——如果有条件一定要用GPU跑。CPU和GPU的性能差距是数量级的。

拿我自己的机器举例:

  • RTX 3060 跑 Qwen3:8b,每秒能出 30-50 个token
  • 纯CPU跑同样的模型,每秒只有 3-5 个token

这是个什么概念?CPU模式下生成一个完整的函数要等几十秒,GPU模式可能几秒就出来了。体验完全不一样的。

所以如果你真的想认真用本地大模型,第一件事——检查你的显卡驱动装好没。NVIDIA驱动要在550以上,AMD需要ROCm,Apple Silicon自带M系列芯片天然支持。

内存比显存更重要

很多人以为显存够就行,其实内存也很重要。因为模型加载的时候,数据是先加载到内存,然后再拷贝到显存。

我的建议:

  • 8B模型:至少16GB系统内存
  • 14B模型:至少32GB系统内存
  • 32B模型:至少64GB系统内存

如果内存不够,可能会用到交换区,那速度会变得很慢。

并发要谨慎

Ollama处理并发请求的能力有限。我之前做过测试,同时发3个请求,结果响应时间变成了原来的3倍——它实际上是串行处理的。

所以我的建议是:

  • 如果是个人使用,直接串行调用就好
  • 如果是Web服务需要并发,用消息队列做限流
  • 或者多开几个Ollama实例,不同端口

预热模型

第一次调用模型的时候会比较慢,因为需要加载模型。但如果你频繁调用,速度就会快很多,因为模型一直在内存里。

如果你是做Web服务,可以在服务启动时先调用一次"预热":

// 在 Program.cs 中
var ollama = new OllamaApiClient(new Uri("http://localhost:11434"));
ollama.SelectedModel = "qwen3:8b";

// 预热 - 发送一个简单请求让它加载
await foreach (var response in ollama.GenerateAsync("hello"))
{
    // 不需要处理响应,只是触发模型加载
}
Console.WriteLine("模型预热完成");

下一步打算啥?

这篇文章写的是最基础的用法。实际上玩了一段时间之后,我发现还有很多有意思的东西可以折腾:

多模态模型:有些模型能看图。比如你扔一张UI截图给它,它能帮你分析布局合不合理。这对于我们做桌面开发的挺有用。

Embedding向量化:Ollama支持生成文本嵌入向量,可以用来做本地知识库。比如把你公司的技术文档都向量化存进去,问问题的时候先检索相关文档再让AI回答,准确性会高很多。

模型微调:如果通用模型在某个垂直领域表现不好,可以试试用微调来让它更专业。不过这个需要一定数据量和算力门槛。

最后提一嘴——本地大模型虽然好用,但它毕竟是个"本地版",跟GPT-4这种顶级闭源模型比还是有差距的。我的使用习惯是:简单问题本地解决,复杂问题还是去问云端API。本地模型作为日常开发的辅助工具,那是相当称职。

好了,絮絮叨叨写了不少,希望对你们有帮助。有问题欢迎评论区聊聊,咱们下回再见。


Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐