Model Overview

Model Summary

Description

Qwen3.8 is the most capable generation in the Qwen open-model family to date. Built on the architectural foundation of Qwen3.5, it delivers substantial gains across coding, professional work, research, and long-horizon agentic tasks. Qwen3.8-27B brings these advances to a compact, deployment-friendly dense model: a native vision-language model that understands images and videos, with flexible thinking control, designed to carry complex, multi-step tasks through to completion with greater reliability. The language model is a 64-layer hybrid that interleaves Gated DeltaNet linear-attention layers with gated full-attention layers (3:1), with a 262,144-token native context that is extensible to 1,000,000 tokens.

Qwen3.8 Highlights

Qwen3.8-27B features the following enhancements:

Core Capabilities: Comprehensive improvements across coding, professional work, research, and long-horizon agentic tasks.

Agent Execution: Stronger autonomous planning and better handling of environment feedback, leading to more reliable end-to-end task completion.

Downstream Compatibility: Broader support for popular harnesses and development tools, making it easier to integrate into your existing stack.

Flexible Thinking Control: Thinking mode is on by default and can be disabled per request; reasoning depth can be tuned with reasoning_effort, and reasoning context from historical messages is retained via preserve_thinking.

Vision-Language Understanding: Native support for image and video understanding, from STEM diagrams and documents to hour-scale videos.

For more details, please refer to the Qwen3.8 Blog, Hugging Face model card and the KerasHub source.

Weights are released under the Apache 2 License. Keras model code is released under the Apache 2 License.

Links

Installation

Keras and KerasHub can be installed with:

pip install -U -q keras-hub
pip install -U -q keras

JAX, TensorFlow, and Torch come pre-installed in Kaggle Notebooks. For instructions on installing them in another environment, see the Keras Getting Started page.

Presets

The following model checkpoints are provided by the Keras team. For each of the presets, we provide code examples in the tab below. Qwen3.8 shares the Qwen3.5 architecture, so it is loaded with the keras_hub.models.Qwen3_5CausalLM classes:

Preset Name Parameters Description
qwen3_8_27b 27 Billion Instruction-tuned 27B dense vision-language model. Built on the Qwen3.5 foundation with substantial gains in coding, professional and research tasks, and long-horizon agentic workflows. Native image and video understanding; thinking mode on by default.

Example Usage

Imports

import keras
import keras_hub
import numpy as np

Load the model

Qwen3.8 shares the Qwen3.5 architecture, so it is served by the Qwen3_5 classes. Load it in bfloat16: the 27B checkpoint is ~55 GB in bf16 versus ~109 GB in float32.

qwen_lm = keras_hub.models.Qwen3_5CausalLM.from_preset(
    "qwen3_8_27b", dtype="bfloat16"
)

Prompt format

Qwen3.8 is instruction-tuned and uses the ChatML template. It thinks by default, emitting its reasoning inside a think block before the answer. Pre-filling an empty think block turns thinking off and gives a direct answer.

# The think tags are spelled "<" + "think>" so they are not stripped as HTML
# when this page is rendered; the resulting strings are the plain tags.
THINK_OPEN = "<" + "think>"
THINK_CLOSE = "<" + "/think>"


def chat_prompt(user_message, thinking=True):
    prompt = (
        "<|im_start|>user\n"
        f"{user_message}<|im_end|>\n"
        "<|im_start|>assistant\n"
    )
    if thinking:
        return prompt + THINK_OPEN + "\n"
    return prompt + THINK_OPEN + "\n\n" + THINK_CLOSE + "\n\n"

Text generation

Use generate() to do text generation.

# Direct answer (thinking disabled).
qwen_lm.generate(chat_prompt("What is Keras?", thinking=False), max_length=64)

# Generate with batched prompts.
prompts = [
    chat_prompt("What is the capital of France?", thinking=False),
    chat_prompt("What is the capital of Japan?", thinking=False),
]
qwen_lm.generate(prompts, max_length=64)

Thinking mode (the default) reasons inside the think block first, so allow a longer max_length.

qwen_lm.generate(chat_prompt("Is 221 a prime number?"), max_length=512)

Custom sampling strategies

Compile the generate() function with a custom sampler. Qwen recommends temperature=0.7, top_p=0.8, top_k=20 for non-thinking mode and temperature=1.0, top_p=0.95, top_k=20 for thinking mode.

# Pass sampler as string.
qwen_lm.compile(sampler="top_k")
qwen_lm.generate(chat_prompt("Write a haiku about the ocean.", thinking=False), max_length=64)

# Qwen-recommended sampling for non-thinking mode.
qwen_lm.compile(sampler=keras_hub.samplers.TopPSampler(p=0.8, k=20, temperature=0.7))
qwen_lm.generate(chat_prompt("Write a haiku about the ocean.", thinking=False), max_length=64)

# Back to greedy decoding.
qwen_lm.compile(sampler="greedy")

Image input

Pass a dict with "prompts" and "images". Mark where the image goes with <|vision_start|><|image_pad|><|vision_end|>; the preprocessor expands the placeholder to the right number of vision tokens.

image_path = keras.utils.get_file(
    "cow_beach.png",
    "https://storage.googleapis.com/keras-cv/models/paligemma/cow_beach_1.png",
)
image = np.array(keras.utils.load_img(image_path))

prompt = chat_prompt(
    "<|vision_start|><|image_pad|><|vision_end|>Describe this image in one sentence.",
    thinking=False,
)
qwen_lm.generate({"prompts": prompt, "images": image}, max_length=128)

Fine-tuning with LoRA

Freeze the backbone and train low-rank adapters on the attention query/value projections, then call fit() on chat-formatted strings.

qwen_lm.backbone.enable_lora(rank=4)
qwen_lm.preprocessor.sequence_length = 128

training_texts = [
    chat_prompt("What is the capital of India?", thinking=False) + "New Delhi<|im_end|>",
    chat_prompt("What is the capital of South Africa?", thinking=False) + "Pretoria<|im_end|>",
]
qwen_lm.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.Adam(1e-4),
    weighted_metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
qwen_lm.fit(x=training_texts, batch_size=1, epochs=1)

Example Usage with Hugging Face URI

Imports

import keras
import keras_hub
import numpy as np

Load the model

Qwen3.8 shares the Qwen3.5 architecture, so it is served by the Qwen3_5 classes. Load it in bfloat16: the 27B checkpoint is ~55 GB in bf16 versus ~109 GB in float32.

qwen_lm = keras_hub.models.Qwen3_5CausalLM.from_preset(
    "hf://keras/qwen3_8_27b", dtype="bfloat16"
)

Prompt format

Qwen3.8 is instruction-tuned and uses the ChatML template. It thinks by default, emitting its reasoning inside a think block before the answer. Pre-filling an empty think block turns thinking off and gives a direct answer.

# The think tags are spelled "<" + "think>" so they are not stripped as HTML
# when this page is rendered; the resulting strings are the plain tags.
THINK_OPEN = "<" + "think>"
THINK_CLOSE = "<" + "/think>"


def chat_prompt(user_message, thinking=True):
    prompt = (
        "<|im_start|>user\n"
        f"{user_message}<|im_end|>\n"
        "<|im_start|>assistant\n"
    )
    if thinking:
        return prompt + THINK_OPEN + "\n"
    return prompt + THINK_OPEN + "\n\n" + THINK_CLOSE + "\n\n"

Text generation

Use generate() to do text generation.

# Direct answer (thinking disabled).
qwen_lm.generate(chat_prompt("What is Keras?", thinking=False), max_length=64)

# Generate with batched prompts.
prompts = [
    chat_prompt("What is the capital of France?", thinking=False),
    chat_prompt("What is the capital of Japan?", thinking=False),
]
qwen_lm.generate(prompts, max_length=64)

Thinking mode (the default) reasons inside the think block first, so allow a longer max_length.

qwen_lm.generate(chat_prompt("Is 221 a prime number?"), max_length=512)

Custom sampling strategies

Compile the generate() function with a custom sampler. Qwen recommends temperature=0.7, top_p=0.8, top_k=20 for non-thinking mode and temperature=1.0, top_p=0.95, top_k=20 for thinking mode.

# Pass sampler as string.
qwen_lm.compile(sampler="top_k")
qwen_lm.generate(chat_prompt("Write a haiku about the ocean.", thinking=False), max_length=64)

# Qwen-recommended sampling for non-thinking mode.
qwen_lm.compile(sampler=keras_hub.samplers.TopPSampler(p=0.8, k=20, temperature=0.7))
qwen_lm.generate(chat_prompt("Write a haiku about the ocean.", thinking=False), max_length=64)

# Back to greedy decoding.
qwen_lm.compile(sampler="greedy")

Image input

Pass a dict with "prompts" and "images". Mark where the image goes with <|vision_start|><|image_pad|><|vision_end|>; the preprocessor expands the placeholder to the right number of vision tokens.

image_path = keras.utils.get_file(
    "cow_beach.png",
    "https://storage.googleapis.com/keras-cv/models/paligemma/cow_beach_1.png",
)
image = np.array(keras.utils.load_img(image_path))

prompt = chat_prompt(
    "<|vision_start|><|image_pad|><|vision_end|>Describe this image in one sentence.",
    thinking=False,
)
qwen_lm.generate({"prompts": prompt, "images": image}, max_length=128)

Fine-tuning with LoRA

Freeze the backbone and train low-rank adapters on the attention query/value projections, then call fit() on chat-formatted strings.

qwen_lm.backbone.enable_lora(rank=4)
qwen_lm.preprocessor.sequence_length = 128

training_texts = [
    chat_prompt("What is the capital of India?", thinking=False) + "New Delhi<|im_end|>",
    chat_prompt("What is the capital of South Africa?", thinking=False) + "Pretoria<|im_end|>",
]
qwen_lm.compile(
    loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
    optimizer=keras.optimizers.Adam(1e-4),
    weighted_metrics=[keras.metrics.SparseCategoricalAccuracy()],
)
qwen_lm.fit(x=training_texts, batch_size=1, epochs=1)
Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. ๐Ÿ™‹ Ask for provider support

Collection including keras/qwen3_8_27b