MAX nightly
This version is still a work in progress.
Models
-
GLM-5.1, GLM-5.2, and GLM-5.3 now default
clear_thinkingtoFalse, which keeps thereasoning_contenta client sends back for earlier turns in the prompt. GLM-5.3 previously defaulted it toTrue, and GLM-5.1's and GLM-5.2's templates clear earlier reasoning when it is unset. This matches Z.ai's Coding Plan endpoint, and Z.ai's thinking-mode guide recommends it for coding and agent workloads. Passclear_thinking=Truein the chat template options to drop earlier reasoning. -
InternVL and Gemma 3 multimodal models now return log probabilities, so
logprobsinmax serveandoutput_logprobsingenerate()work for them. -
Added support for
poolside/Laguna-S-2.1-NVFP4on theLagunaForCausalLMarchitecture. The checkpoint interleaves 12 full-attention layers with 36 sliding-window layers on a 512-token window, gates attention output per head rather than per element, and mixes expert precisions: NVFP4 routed experts in layers 1-39 and bfloat16 ones in layers 40-47, with the dense first layer and every shared expert in bfloat16 throughout. Attention type, query-head count, RoPE scaling and MLP precision are now all resolved per layer, sopoolside/Laguna-M.1-NVFP4keeps its uniform layout.
Inference server
-
/v1/completionsnow returns a 400 error forechowithlogprobson a server started without--enable-echo, when the model computes log probabilities. The request used to crash the model worker. Other models still return no log probabilities, andechowithoutlogprobshas no effect. -
Qwen 3.5 now passes every
chat_template_kwargsentry to its chat template, such asadd_generation_promptorpreserve_thinking. It used to keep onlyenable_thinking, which still defaults to on. -
MAX Serve's span attributes now sit under the entity they describe:
max.request_idis nowmax.request.id,max.batch_idismax.batch.id, and the other request and batch attributes move undermax.request.*andmax.batch.*.kernel_agg:<name>spans carry the kernel name inmax.kernel.opinstead ofmax.kernel.name; per-kernel spans keepmax.kernel.name. Update trace queries that filter on the old names.
Server metrics
-
maxserve_batch_execution_time_millisecondsnow measures the executor call when overlap is off. The former full scheduler-step duration is available asmaxserve_scheduler_batch_step_time_milliseconds. Batch throughput uses the executor interval and counts returned output tokens, including prefill outputs and accepted speculative tokens. See the batch timing contract for overlap timing. Intermediate prefill chunks returning no tokens now contribute zero to the CE generation-throughput histogram, shifting its distribution on chunked-prefill workloads. The benchmark harness'sprefill_batch_execution_time_msanddecode_batch_execution_time_mscolumns usemaxserve_batch_execution_time_milliseconds, so MAX's synchronous values now exclude scheduler work; comparisons with older builds are not like for like. -
Added
maxserve_request_client_disconnect_cancellations_total, which counts streamed responses cut short by a client disconnect and completion requests abandoned while waiting for a result, split byphase. A mid-stream disconnect was previously invisible:maxserve_request_count_totalrecords it under the status code already sent.
max CLI
max generateruns image generation models such as FLUX.2 and saves the images to--output, which defaults toimage.pngand whose suffix picks the format. A path without a suffix is a directory, created with its parents if missing. It adds--negative-prompt,--height,--width,--num-inference-steps,--guidance-scale,--true-cfg-scale,--strengthand--num-images-per-promptfor them, and takes the input image from--image_url. A flag the user set for the other kind of model, including--max-batch-sizeand--device-graph-captureon an image model, raises an error before anything loads. Text models run as before.
Python API
-
A custom op whose Mojo kernel is registered wrong, or doesn't match its call, now raises
max.graph.KernelRegistrationError, aValueErrorsubclass. The message points at the Mojo code the way the Mojo compiler does: the file, line and column, the source line and a caret. A custom op package's kernels are now checked when it loads, so a misplacedDeviceContextargument fails there instead of while compiling the generated code. -
Source tracebacks (
InferenceSession.debug.source_tracebacks) now stop at your code's call into the graph API, so errors point at the lines that created each op rather than at MAX's implementation of it. Setsource_tracebacks_hide_internal = Falseto keep every frame. Errors thatModel.executeraises are nowmax.engine.ModelExecutionError, aRuntimeErrorsubclass that is also aValueError, so existingexcept ValueErrorhandlers keep working. -
max.nn.Transformer,DistributedTransformer,logits_postprocess()anddistributed_logits_postprocess()return amax.nn.graph_groups.DecoderOutputsof named groups instead of a tuple. Read the logits asoutputs.logits["logits"]rather thanoutputs[1], and pass the outputs toGraph.output(outputs)rather than unpacking them. The graph's outputs keep their order, andGraph.output_treereturns them by name. -
ops.matmul()takes anout_dtypekeyword argument that sets the result dtype. The inputs keep their own dtypes and are no longer cast to a common one, sobfloat16inputs without_dtype=DType.float32keep the float32 accumulator. Withoutout_dtype,ops.matmul(), the@operator andmax.experimental.functional'smatmul()require both inputs to share a dtype, which the result keeps, somax.nn.Linearraises when its input and weight dtypes differ.max.experimental.nn.Linearcasts its input to the weight's dtype, so a float32 input to a bfloat16Linearnow gives bfloat16 instead of float32. -
Added
max.driver.Buffer.memset(value)to fill a contiguous buffer or slice in place with a byte value, without allocating a source buffer. -
Added the
ops.CoordinateTransformModeandops.RoundModeenums.ops.resize_linear,ops.resize_nearest, and theirmax.experimentalfunctional counterparts take them forcoordinate_transform_modeandround_modein place of the integer codes 0-3. The integer codes still work. -
Added
max.driver.float32_to_bfloat16_as_uint16(),max.driver.bfloat16_as_uint16_to_float32(), andmax.driver.float32_array_to_buffer()for moving bfloat16 data between NumPy and aBuffer.max.pipelines.libstill re-exportsfloat32_to_bfloat16_as_uint16()andfloat32_array_to_buffer(). -
Removed the
max.pipelines.lib.bfloat16_utilsmodule. Import its functions frommax.driverinstead. -
Removed
GraphPipelineModel,ModuleV3PipelineModelandMultiGraphPipelineModelWithKVCachefrommax.pipelines.lib. SubclassPipelineModel, return the module fromgraph_specs()underGraphRole.ENCODER, and load it withload_graphs()instead. -
Buffer.to(device)for a buffer on another queue of that device requires anafterevent and no longer syncs implicitly. Mojo'sreassign_ownership_to()is removed; useenqueue_wait_for()and keep buffers alive until synced. -
Removed
max.nn.FusedMLP. Usemax.nn.MLP, which takes the separategate_projandup_projweights a checkpoint ships and fuses their matmuls in the graph. -
Removed
max_single_alloc_size()andrun_healthcheck()from the MojoDeviceContext. On NVIDIA and AMD GPUs the health check still runs when a device context is created. Python'sDevice.max_single_alloc_sizeis unchanged. -
generate(),GenerateIterator,AutoModel.generate()andAutoModel.generate_iter()takemessagesandimages, so they run chat and image prompts on models such as Gemma 3 and InternVL.GenerateResult.prompt_lengthsgives each prompt's length.AutoModelturns the vision encoder cache off by default. -
Added
AutoProcessortomax.pipelines, which turns chat conversations with inline images into themessagesandimagesthatAutoModel.generate()takes. -
AutoPipelinenow takes a chat conversation, or a list of them, with images inline, and returns only the new text for each one. -
generate(),GenerateIterator,AutoModel.generate(),AutoModel.generate_iter()andAutoPipelinetakechat_template_kwargs, which reach the chat template of everymessagesrow, such as{"enable_thinking": False}to turn off Qwen 3.5's thinking. -
imagesongenerate(),GenerateIterator,AutoModel.generate()andAutoModel.generate_iter()takes PIL images as well as encoded bytes, and the tokenizer uses a PIL image without decoding it again.AutoProcessornow passes a copy of each PIL image instead of encoding it. -
AutoProcessorandAutoPipelinenow read images from http(s) URLs,data:URIs and base64 data, including OpenAIimage_urlparts. URLs are fetched with a 30 second timeout and a 100 MiB limit per image. -
Errors from
AutoModel.generate()andAutoModel.generate_iter()now nameAutoModel, notgenerate(). An image part that holds its own image, or animage_urlpart, now raisesValueErrorpointing toAutoProcessor, even whenimagesgives the row the right count. -
Added
generate_images()inmax.pipelines.lib.generate_images, which runs an image generation pipeline such as FLUX.2 in your own process and returns one list of PIL images per prompt, in prompt order. Each prompt runs as the requestmax servebuilds for it, so the model's defaults are the same as in serve. -
AutoModelnow loads image generation models such as FLUX.2, andAutoModel.generate(prompt=...)runs them and returns aGenerateImagesResult. Text models keep the samegenerate()call and result. An argument for the other kind of model raises aValueErrorthat names it. Image models needmax_batch_size=1and no device graph capture.AutoPipelineruns image models too, throughAutoModel.generate(prompt=...), and returns a prompt's list of images. On an image model,AutoModel.generate_iter()points toAutoModel.generate(prompt=...), andgenerate()points togenerate_images(). -
generate_images(),AutoPipelineand the image path ofAutoModel.generate()takeimages, one list of input images per prompt as for textgenerate(), each image encoded bytes or a PIL image. A list holds one image, or none for a text-only prompt. FLUX.2 and Z-Image run image to image this way. They also takestrength, which Z-Image reads and FLUX.2 ignores.ZImagePipelinenow lists image input. -
generate()andGenerateIteratorraise the sameTypeErrorfor a bare image or bytes passed asimagesas for any otherimagesthat is not one list of images per conversation, instead of a Python iteration error. -
Removed the
blocking_sync,disable_timingandinterprocessparameters from the MojoDeviceContext.create_event(), along withEventFlags. Events never record timing; useDeviceContext.execution_time()to time kernels.
Kernels and GPU programming
-
The deprecated
LayoutTensorandLayoutTensorItertypes have been removed from thelayoutpackage, along with theto_layout_tensor()andlt_to_tt()conversions. UseTileTensorandTileTensorIterinstead.ThreadScopenow lives inlayout.tile_io. -
TileTensor.tile()on a nested layout now indexes each mode first leaf first, ascrd2idxand the CuTelocal_tileoperation do: tilecof sizetstarts at elementc * tof the mode, and the tile keeps the leading leaves it spans, with their strides. Before, it treated the first leaf of each mode as the tile grid and the second as the fragment. To extract fragments, put the fragment first in each mode, asblocked_product()does:((3, 2), (4, 2))rather than((2, 3), (2, 4)). A tile size that doesn't split a nested mode on leaf boundaries is now a compile-time error. -
TileTensor.load_linear()andTileTensor.store_linear()have been removed. UseTileTensor.load()andTileTensor.store()with aCoord, which also work on nested layouts:t.load_linear[4](IndexList[2](r, c))becomest.load[4](Coord(r, c)). On CPU,load()andstore()default to the element's alignment rather than the full vector's, so passalignment=to keep a wider one. -
Removed the
max,min,sum,productandmeanoverloads inmax.algorithmthat take an input shape, aninput_fn, anoutput_fnand areduce_dim. To reduce along an axis, use the graph ops (ops.sum,ops.max,ops.min,ops.mean,ops.prod), or callmap_reduceonce per row. TheSpanoverloads and the 1-Dsumandmeanoverloads that take a length and aninput_fnare unchanged.
Tools
- Added the ability to install
max-serveandmax-benchmarkas Conda extras, usingmax[extras=[serve]]andmax[extras=[benchmark]]respectively.max[extras=[all]]installs both plusmojo.
Fixes
-
max servenow runs prompts given as token ids, with no images, on Gemma 3, InternVL, Idefics3, Pixtral and Qwen 2.5 VL. These requests used to fail in the tokenizer. -
max servenow returns a 400 for a prompt given as token ids, on completions, chatprompt_tokensor embeddings, when an id is below 0 or past the model's vocabulary. Such a request used to fail its whole batch.generate(),GenerateIteratorandAutoModel.generate()raiseInputErrorfor such an id ininput_ids. Embeddings requests the tokenizer rejects, such as a too-long prompt, now return a 400, not a 500.
Mojo language
For all the updates to the Mojo language, standard library, and tools, see the Mojo release notes.