GENIEE TechBlog
👌

話題沸騰中のdeekseek-r1をローカルで動かしてみました

2025/01/24に公開

ジーニーのCTOの孟です。

deepseek-r1の性能詳細については説明を割愛させていただきます(近頃はGPT-4oを超える性能を持つLLMが自宅環境でも構築できるようになり、恐怖さえ感じますね...)。
RAGやアプリケーションの研究用として、OpenAIのChat Completionsと互換性を持つFastAPIサーバーを書いてみました。

※DeepSeekの公式APIを使用する場合、オプトアウトが明記されていないとデータの安全性に懸念が生じるため、deepseek-r1をローカル環境で構築することでデータを海外に送ることなく、セキュアな環境を整えることを目的としています。弊社ジーニーではお客様のデータを多く扱っておりますので、セキュリティ面を常に意識してプロダクトを開発しております。
※新卒入社時には「安全なWebアプリケーションの作り方」という本を全員に配布し、ブートキャンプで使用しています。これにより、新入社員がセキュアな開発手法を習得し、プロダクトの安全性を高めることを目的としています。

クライアント用のサンプルコードも用意していますので、必要に応じてご自由にお使いください。
gradioを用いてローカルでchatできる環境もしてみたので、gradioのコードもぜひ試してみてください。

自宅環境のRTX4090(24GB VRAM)では7Bモデルまでの動作確認が完了しています。大規模モデルを動作させるにはメモリ使用量を削減するための量子化が必要ですが、現在は効率性と性能のバランスが取れた7Bモデルで検証を進めています。
※更新:ollama + 32bの記事も作成しました。個人向けであれば、vllm+7Bよりもollama+32Bの方が性能的に優れていると思いますので、参考にしてください。

他のモデルもメモリサイズに合わせて使い分けると効率的かもしれません。
https://huggingface.co/collections/deepseek-ai/deepseek-r1-678e1e131c0169c0bc89728d

実際に使ってみての気づき:

  1. 推論性能が非常に高く、NSFWフィルタリングなしで使いやすい
  2. 日本語処理はQwenベースの影響かやや弱め。解決策として、英語/中国語でプロンプト作成→回答生成→OpenAI/deepseek-v3 APIで翻訳 のワークフローが効果的、
      ・日本語で質問をしても、英語、中国語、日本語の3か国語が混ざって返ってくる場合がある。
      ・カタカナが苦手で、例えば弊社の「ジーニー」という社名が「Genie」や「ジーニ」と返ってくることがある。
  3. vLLMのGPUメモリ使用率を調整すれば、RTX4090(24GB)でピタリと収まるのが助かる
      ・GPUメモリ使用率(gpu_memory_utilization)を調整することで、GPU RAMの使用効率を最適化できます。また、蒸留モデルは省エネ設計のおかげで、4090 24GBのメモリでもR1レベルを動作させることができ、非常に便利です

その他:
面白いプロダクト開発に共に挑戦するエンジニア募集中!
弊社Genieeで世界を変えるサービスを一緒に作りましょう。
採用情報はこちら → https://geniee.co.jp/recruit/

server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from vllm import LLM, SamplingParams
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

MAX_INPUT_TOKEN = 1024 * 8
MAX_OUTPUT_TOKEN = 1024 * 8
TEMPERATURE = 0.75 # 0.6 - 0.95

model = LLM(
    model="deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
    dtype="float16",
    gpu_memory_utilization=0.75,
    max_model_len=MAX_INPUT_TOKEN,
    tensor_parallel_size=1,
)

sampling_params = SamplingParams(
    temperature=TEMPERATURE,
    top_k=50,
    top_p=0.95,
    max_tokens=MAX_OUTPUT_TOKEN,
    n=1
)

app = FastAPI()

class Message(BaseModel):
    role: str
    content: str

class ChatCompletionRequest(BaseModel):
    model: str
    messages: list[Message]
    max_tokens: int = MAX_INPUT_TOKEN
    temperature: float = TEMPERATURE
    top_p: float = 0.95
    top_k: int = 50
    n: int = 1

class ChatCompletionChoice(BaseModel):
    message: Message
    index: int
    finish_reason: str

class ChatCompletionResponse(BaseModel):
    choices: list[ChatCompletionChoice]

@app.post("/v1/chat/completions")
async def create_chat_completion(request: ChatCompletionRequest):
    try:
        if not request.messages:
            raise HTTPException(status_code=400, detail="Messages cannot be empty")
        
        prompt = ""
        for message in request.messages:
            prompt += f"{message.role}: {message.content}\n"
        
        prompt = f"<think>{prompt}</think>"
        
        outputs = model.generate(prompt, sampling_params)
        
        choices = []
        for output in outputs:
            choices.append(ChatCompletionChoice(
                message=Message(role="assistant", content=output.outputs[0].text),
                index=0,
                finish_reason="length"
            ))
        
        return ChatCompletionResponse(choices=choices)
    
    except HTTPException as e:
        raise e
    except Exception as e:
        logger.error(f"Error generating chat completion: {e}")
        raise HTTPException(status_code=500, detail="Internal server error")

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
client.py
from openai import OpenAI

def chatgpt_chat(msg:str="", system:str="", gpt_model:str="deepseek-r1", max_tokens:int=10240, temperature:float=0.0, messages=None):
    client = OpenAI(api_key="dummy", base_url="http://127.0.0.1:8000/v1")
    if not messages:
        messages = [{"role": "system", "content": system}] if system else []
        if msg:
            messages.append({"role": "user", "content": msg})
    
    completion = client.chat.completions.create(
        model=gpt_model,
        messages=messages,
        max_tokens=max_tokens,
        temperature=temperature,
    )
    return completion.choices[0].message.content

print(chatgpt_chat("hello"))

gradioでdeepseek-r1のapiサーバと繋いでchat環境を構築してみる

gradio.py
import gradio as gr
import requests

CHAT_SERVER_URL = "http://127.0.0.1:8000/v1/chat/completions"
API_KEY = "your_api_key_here"
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {API_KEY}"}

def respond(message, chat_state):
    if chat_state is None:
        chat_state = []

    chat_state.append({"role": "user", "content": message})
    payload = {
        "model": "deepseek-r1",
        "messages": chat_state
    }

    try:
        response = requests.post(CHAT_SERVER_URL, json=payload, headers=headers)
        response.raise_for_status()
        assistant_message = response.json()['choices'][0]['message']['content'].strip()
        chat_state.append({"role": "assistant", "content": assistant_message})

        conversation = ""
        for msg in chat_state:
            if msg["role"] == "user":
                conversation += f"**あなた**: {msg['content']}\n\n"
            else:
                conversation += f"**Deepseek**: {msg['content']}\n\n"

        return "", chat_state, conversation

    except requests.exceptions.RequestException as e:
        return "", chat_state, f"エラー: {e}"

with gr.Blocks() as demo:
    gr.Markdown("<h1><center>Chat with Deepseek</center></h1>")
    chat_state = gr.State([])
    conversation_box = gr.Markdown(label="会話履歴", elem_id="conversation_box")

    msg = gr.Textbox(label="あなたのメッセージ", placeholder="メッセージを入力してください")
    clear_btn = gr.ClearButton([msg, conversation_box], value="クリア", elem_id="clear_button")

    msg.submit(respond, [msg, chat_state], [msg, chat_state, conversation_box])

if __name__ == "__main__":
    demo.launch(server_name="0.0.0.0", server_port=7860)

ローカルでdeepseek-r1とchatできる環境が整いました。

おまけ:
curl clientサンプル:

curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{"model": "deepseek-r1","messages": [{"role": "system", "content": "You are a python engineer"}, {"role": "user", "content": "give me the sample flask python code"}]}'
GENIEE TechBlog
GENIEE TechBlog

Discussion