How To: Using the Mila Inference Server with Claude Code and Codex CLI #10
Replies: 6 comments 4 replies
|
Hey @ToddThomson, curious how Claude Code is handling your Mila setup so far? Are you hitting those weird context walls where it starts losing track of files, or is it staying snappy? |
|
Update — Gemma 4 12B and working tool calling Quick follow-up, since the article ended on "tool calling is coming next": it's here, and it's working. MIS now serves Gemma 4 12B Instruct (FP4) on the same 12 GB RTX 4070 — the full 16384-token context fits with ~1.5 GB to spare, thanks to FP4 weights and a sliding-window KV cache (40 of 48 layers stay pinned at a 1024 window; only the 8 global layers grow with context). Gemma was the prerequisite for tool calling — Llama 3.x through MIS strips tool schemas, whereas Gemma has a trained native tool-call protocol. And Codex CLI tool calling now works end-to-end against MIS. Point Codex ( A few things that made it work, in keeping with the article's honest-account style:
Next up: bringing Claude Code's |
|
Update 2 — Gemma 4 12B and working tool calling with Claude Code CLI Claude Code CLI tool calling now works end-to-end against Mila Inference Server. Due to Claude's extensive tool calling support the context window needed to be bumped to beyond 40,000 tokens. Again the FP4 weights and sliding-window KV cache came to the rescue and Mila's GemmaModel looks like it can handle 56K or more even on a RTX 4070 with 12GB. With the larger context window required by Claude we surfaced a nasty issue that brought a large tax to our GQA operation. The fix will bring perhaps a 2x perf increase in the prefill phase which is a huge deal when working at the edge with Claude code. |
|
As an added bonus, we are also testing the Mila Inference Server with Hermes using the MIS OpenAI compatible protocol. Early stages right now, but the testing looks promising! |
This comment was marked as spam.
This comment was marked as spam.
|
Update 3 — Flash Attention has landed in Gemma 4 Closing the loop on the last two updates: both the ~2x prefill fix and the promised Flash Attention implementation are now in. Mila's GQA operation now runs a hand-written FlashAttention prefill for Gemma 4 — two kernels (an HS-split kernel for the global layers at head dim 512, a row-split FA-2 kernel for the sliding layers at 256), plus an FP8 tensor-core GEMM on the linear path. No CUTLASS, just CUDA C++ and raw mma.sync PTX. The headline: on a single RTX 4070 (12 GB), Gemma 4 12B FP4 prefill at 48K context went from ~1.95x behind llama.cpp to ~1.14x — near parity, on the same card. And because online softmax never materializes the attention score matrix, we reclaimed ~1–2 GB at long context, which is what lets a 12 GB card hold 64K — exactly the regime Claude Code's harness pushes us into. I wrote up the full journey — the dead ends, the profiler surprises, and the numbers with their caveats — as a separate Show and tell post: Hand-writing FlashAttention + an FP8 GEMM for Gemma 4. The short version is that it took ten sessions and more falsified assumptions than wins to get here. |
Uh oh!
There was an error while loading. Please reload this page.
How To: Using the Mila Inference Server with Claude Code and Codex CLI
Mila Discussions — Inference Server Series
Introduction
One of the goals of the Mila Inference Server (MIS) is to make a locally-running Llama model feel, from the outside, indistinguishable from a cloud API. If MIS does its job correctly, you should be able to point Claude Code or Codex CLI at
http://localhost:8000and have them work as if they were talking to Anthropic or OpenAI's servers — except your data never leaves your machine and your GPU is doing the work.Getting there turned out to be more interesting than expected. This article is an honest account of what it took to connect both clients to MIS, the surprises we hit along the way, and the exact fixes that made it work. It covers:
At the time of writing, MIS runs Llama 3.2 3B Instruct at CUDA BF16 on an RTX 4070. It is the only supported model for now. The Alpha.4 roadmap adds Llama 3.1 8B Instruct at FP8, which will be the first tool-calling capable model.
What Is the Mila Inference Server?
MIS is a FastAPI-based inference server that sits in front of Mila's C++ inference engine via a pybind11 binding. It exposes three OpenAI-compatible endpoints:
POST /v1/chat/completions— the standard Chat Completions APIPOST /v1/completions— the legacy Completions APIPOST /v1/responses— the newer OpenAI Responses APIThe protocol is selected via the
MILA_PROTOCOLenvironment variable in.env. For both Claude Code and Codex CLI, set:The server does not execute tools. It parses requests, builds a correctly-formatted Llama 3.x instruct prompt, runs inference through the Mila C++ engine, and returns a structured response. Tool calling support is on the roadmap and is discussed at the end of this article.
A minimal
.envfor getting started:Connecting Claude Code
Claude Code is Anthropic's terminal-based coding assistant. It connects to any OpenAI-compatible server via a base URL override and uses the standard
/v1/chat/completionsendpoint.Getting it working took a couple of days of iteration — the details of early false starts are lost to history, but the working configuration is straightforward once you know the pitfalls.
Working configuration
Point Claude Code at MIS with:
Or set it persistently in Claude Code's config. It will use
/v1/chat/completionsand the standard chat message format —system,user,assistantroles,contentas a plain string. MIS handles this cleanly.Windows-specific: DLL resolution
On Windows, Python cannot find the CUDA runtime DLLs unless you explicitly add the CUDA bin directory to the DLL search path before importing the pybind11 module. This must happen at the very top of your FastAPI application, before any Mila import:
Without this, the import fails with a cryptic
DLL load failederror that gives no hint about which DLL is missing or why.Mila initialization placement
Mila::initialize()must be called in the pybind11 module entry point, not lazily on first use. If you defer it, the first inference call will either crash or produce garbage. This is handled internally in the binding — just ensure the module is imported before your FastAPI app starts serving requests.Special token registration
During early development, the BPE tokenizer was only registering 2 special tokens instead of the 7 that Llama 3.2's chat template requires. The fix was to register
extended_special_tokensbefore callingbuildSpecialTokenList(). If you see malformed prompt boundaries or the model fails to respect the instruct format, check your special token count — it should be 7 for Llama 3.2.Result
With these issues resolved, Claude Code connected cleanly and produced coherent, high-quality output. The
/v1/chat/completionspath in MIS is straightforward — Claude Code sends well-structured requests with no surprises.Connecting Codex CLI
This is where things got interesting.
Codex CLI is OpenAI's terminal-based coding agent. It is open source and under active development. We were running a recent version (post-0.122.0) and assumed it would work the same way as Claude Code — point it at MIS, set the model name, get responses.
That assumption was wrong in almost every detail.
Surprise 1: Codex CLI uses a different API entirely
Claude Code uses
/v1/chat/completions. Codex CLI, in recent versions, uses/v1/responses— the OpenAI Responses API, which is a newer and structurally different endpoint.The first sign of this was a debug log showing the request arriving at
parse_responses_requestinstead ofparse_chat_request. MIS already had a/v1/responsesroute implemented, so the request was being handled — but the output was garbage.The Responses API differs from Chat Completions in several ways:
instructionsfield at the top level, not as asystemrole message insidemessagesinputfield, notmessagesinputcan be a plain string, or an array of typed message objects{"type": "input_text", "text": "..."}outputarray with typed items rather than achoicesarrayIf your server is built for Chat Completions and you try to handle Responses API requests with the same parsing logic, you will extract empty strings or raw Python list objects as the user message, feed them to
build_instruct_prompt, and the model will generate nonsense.The fix is a proper content extractor:
Surprise 2: The
developerroleCodex CLI sends its first input item with
"role": "developer"— not"system". This role carries Codex's own sandbox permissions and safety instructions. It is functionally a system message and must be treated as one.If your parser only checks for
role == "system"to extract the system prompt, thedeveloperblock falls through as a history turn with an unrecognised role. Llama's chat template has nodeveloperrole. The resulting prompt is malformed and the model generates garbage.The fix is to consume all leading
systemanddeveloperrole items into the system prompt block:This is not documented in the Responses API spec. We found it by logging the raw request body.
Surprise 3: Multiple consecutive
userturnsAfter consuming the
developerblock, Codex CLI sends two consecutiveuserturns before the actual user message:<cwd>,<shell>,<current_date>,<timezone>A naive parser that takes
messages[-1]as the user message and everything before as history will produce a history containing auserturn immediately followed by anotheruserturn — with noassistantturn in between. This is invalid for Llama's instruct template, which expects strictly alternatinguser/assistantpairs.The fix is to collapse consecutive same-role turns by merging their content:
With this in place, the environment context and the user message are merged into a single coherent user turn.
Surprise 4: The tool definition token flood
After fixing the parsing issues, MIS was producing output — but it was bizarre schema-like text rather than a response to "Hi". The debug log showed 11,346 tokens for a simple greeting.
The cause: Codex CLI sends its full set of shell tool definitions in the
toolsfield of every request. These includecontainer.exec,container.write_file, and several others, each with detailed JSON schemas. MIS was faithfully serializing all of them into the system block viabuild_instruct_prompt.11,000 tokens of JSON schema in the context, followed by a
userturn of "Hi", is not a situation Llama 3.2 3B handles gracefully. With only 1,024 tokens of generation budget remaining, the model pattern-matched on the schema-heavy context and produced schema-like text. It was not a model failure — it was a context failure.The fix is simple: MIS does not execute tools. Strip them entirely:
After this single change, the same "Hi" prompt produced:
Coherent, contextually aware, correctly formatted. Four surprises, four fixes.
The model metadata warning
After getting coherent output, Codex CLI displays a warning on startup:
This is a Codex CLI client-side issue, not a MIS issue. Codex ships with a built-in table of context windows and capabilities for OpenAI's own models. Any non-OpenAI model slug triggers this warning. Everything still works correctly.
To silence it, add to
~/.codex/config.toml:Match these values to your
MILA_CONTEXT_LENGTHandMILA_DEFAULT_MAX_NEW_TOKENS.Working Codex CLI configuration
The Responses API Streaming Protocol
For completeness, the full SSE event sequence MIS emits for a streaming Responses API request is:
Codex CLI expects this exact sequence. The
response.completedevent carries the full assembled response in itsoutputarray, which is how Codex CLI renders the final reply.What's Coming: Tool Calling
Codex CLI is a coding agent — it reads files, writes patches, and runs shell commands. None of that works without tool calling, because those capabilities are exposed as tools that the model must invoke.
MIS currently strips all tool definitions from Codex CLI requests. This is correct for Llama 3.2 3B, which does not have reliable tool calling support. The Alpha.4 roadmap changes this:
Alpha.4 adds Llama 3.1 8B Instruct at FP8 — the first tool-calling capable model in Mila. With it, MIS will gain:
ToolCallParserintegration via pybind11 — detecting<|python_tag|>-prefixed tool calls in model output and converting them to structuredfunction_callresponse itemsfunction_call_outputinput items mapped to Llama'sipythonroleMILA_TOOL_CALLING_ENABLEDconfig flag gating the behaviourThe full design is specified in
specifications/toolcalling.md.Once tool calling is working, Codex CLI will be able to use MIS as a genuine local coding agent backend — reading your codebase, proposing edits, and running commands, entirely on your own hardware.
Summary
/v1/chat/completions/v1/responsessystemrole inmessagesinstructionsfield +developerroledata: {...}SSE chunksBoth clients are now connected and producing coherent output from a local Llama 3.2 3B Instruct model running on an RTX 4070 via Mila's CUDA BF16 inference engine. No data leaves the machine. No API key required.
Mila is an open source C++23/CUDA LLM inference framework. This article documents real development experience and will be updated as MIS matures.
All reactions