I tried deploying Nemotron 9B-v2-Japanese to SageMaker Tokyo Region with a VPC closed network configuration

I tried deploying Nemotron 9B-v2-Japanese to SageMaker Tokyo Region with a VPC closed network configuration

I tried deploying Nemotron 9B-v2-Japanese to SageMaker Tokyo region in a closed VPC environment. I have summarized important tips that are easy to overlook during implementation, such as specifying the correct chat template for the model and key configuration points for the LMI container.
2026.02.21

This page has been translated by machine translation. View original

Introduction

Hello, I'm Morishige from Classmethod's Manufacturing Business Technology Department.

In my previous article, I ran NVIDIA's officially enhanced Japanese LLM Nemotron 9B-v2-Japanese on DGX Spark and tested it in various scenarios including Tool Calling and RAG.

Local verification on DGX Spark is convenient, but things change when it comes to "how to run it in a production environment." In cases involving personal information or confidential data, a closed-network configuration where data never leaves Japan is often required.

In this article, I tried deploying Nemotron 9B-v2-Japanese to Amazon SageMaker in the Tokyo region and getting it running in a closed-network configuration within a VPC. Since I'm comparing with the same benchmark (JCommonsenseQA) as last time, you should be able to see the differences between local and cloud environments.

Before Running on SageMaker

Using vLLM as the Inference Engine

In my previous article, I confirmed that the NGC container version of vLLM (v0.13.0) works on DGX Spark as well, but on SageMaker, vLLM is used as a backend via the LMI container.

You specify vLLM as the backend through SageMaker's LMI (Large Model Inference) container. Since LMI containers V17 (September 2025) and later, async mode integrating vLLM's AsyncLLMEngine has become the default. This time, I'm launching in async mode with a configuration that specifies the chat template using OPTION_ROLLING_BATCH=disable and OPTION_CHAT_TEMPLATE.

JumpStart / Marketplace Status

As of February 2026, Nemotron 9B-v2-Japanese is not registered in SageMaker JumpStart. The English version Nemotron nano 9b v2 is registered on AWS Marketplace, but the Japanese version is not. You'll need to configure and deploy using the LMI container yourself.

Deployment Configuration

Solid lines represent the inference request flow, and dashed lines represent model loading at deployment time. All communications are completed within the VPC.

  • The model is downloaded from HuggingFace Hub to S3 in advance. No internet access from the endpoint is required.
  • Calls to SageMaker Runtime go through VPC endpoints. Data never leaves the VPC.
  • The inference engine is vLLM inside the LMI container. It fits within the A10G (24GB) at BF16 full precision.
Item Configuration
Instance ml.g5.2xlarge (1x A10G, 24GB VRAM)
Region ap-northeast-1 (Tokyo)
Container SageMaker LMI V20 (DJL 0.36.0 + vLLM)
Operation Mode async mode (OPTION_ROLLING_BATCH=disable)
Model Precision BF16 (17.8GB)
max_model_len 4096

The reason I chose ml.g5.2xlarge is that the total of BF16 (17.8GB) + vLLM overhead (~1GB) + KV cache (~2-3GB for 4 Attention layers) comes to approximately 21GB, which fits within the A10G's 24GB. With the Mamba-2 hybrid architecture, only 4 out of 56 layers are Attention layers, so the KV cache is about 1/14 of what it would be with all-Attention layers.

Deployment Steps

Placing the Model in S3

In a closed-network configuration, the endpoint cannot access HuggingFace Hub. You need to download the model in advance and upload it to S3.

Only this step requires an internet connection. The execution environment requires AWS CLI (v2.32.0 or later) and HuggingFace CLI (hf). hf can be installed with brew install huggingface-cli, or if you have uv, you can use it without installation via uvx hf. Make sure you have at least 20GB of free disk space (model size is approximately 18GB). This can be run from anywhere that meets the conditions, whether your local PC or EC2.

For AWS CLI authentication, aws login is convenient. It opens a browser where you can sign in the same way as the management console, and temporary credentials are automatically rotated. No login is required for hf since the model is public.

# Download model from HuggingFace (approximately 18GB)
hf download nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese \
  --local-dir /tmp/nemotron-9b-japanese

# Upload to S3 (bucket name is arbitrary, match it with subsequent steps)
aws s3 cp /tmp/nemotron-9b-japanese \
  s3://<your-bucket>/models/nemotron-9b-v2-japanese/ \
  --recursive

The S3 bucket name is arbitrary, but using SageMaker's default bucket (sagemaker-{region}-{account-id}) makes it easier to align with subsequent steps. Calling sagemaker.Session().default_bucket() will automatically create it, so you don't need to create the bucket in advance.

When executing with Python SDK

You can do the same thing with the Python SDK instead of AWS CLI. By delegating bucket creation and naming to sagemaker.Session().default_bucket(), you don't need to worry about the bucket name.

from huggingface_hub import snapshot_download
from sagemaker.s3 import S3Uploader
import sagemaker

sess = sagemaker.Session()
bucket = sess.default_bucket()

model_dir = snapshot_download(
    "nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese",
    local_dir="/tmp/nemotron-9b-japanese"
)

s3_model_uri = S3Uploader.upload(
    local_path=model_dir,
    desired_s3_uri=f"s3://{bucket}/models/nemotron-9b-v2-japanese/"
)
print(f"Model uploaded to: {s3_model_uri}")

All subsequent steps are completed within the VPC.

Creating VPC Endpoints

To run the SageMaker endpoint in a closed-network configuration, you create interface endpoints within the VPC to confine communications to AWS services within the VPC.

Six VPC endpoints are required.

Endpoint Type Purpose
com.amazonaws.ap-northeast-1.sagemaker.api Interface SageMaker API
com.amazonaws.ap-northeast-1.sagemaker.runtime Interface Inference calls
com.amazonaws.ap-northeast-1.s3 Gateway Model artifact retrieval
com.amazonaws.ap-northeast-1.ecr.dkr Interface Container image retrieval
com.amazonaws.ap-northeast-1.ecr.api Interface ECR API
com.amazonaws.ap-northeast-1.logs Interface CloudWatch Logs (recommended)

The top 5 are mandatory for deployment, and adding CloudWatch Logs for debugging gives peace of mind. In a completely closed VPC, container logs won't reach CloudWatch, making it impossible to trace the cause of deployment failures. S3 is a Gateway type (free), while the others are Interface types (ENI charges apply).

For Interface type endpoints, allow HTTPS (443) from the VPC CIDR in the security group inbound rules and enable private DNS. With private DNS enabled, you can communicate through the closed network without changing the SDK code.

Creating a SageMaker Model

From the SageMaker section of the AWS console, create a model. Specify the vLLM backend for the LMI container and pass Nemotron-specific settings via environment variables.

There are two environment variables that are easy to overlook.

OPTION_TRUST_REMOTE_CODE=true is required to load the custom model code for the Mamba-2 hybrid architecture. Without it, model loading itself will fail.

OPTION_MAMBA_SSM_CACHE_DTYPE=float32 specifies the precision of the Mamba-2 layer's state cache. Omitting it will degrade inference result precision. Since it won't cause an error, it's easy to miss, so be sure to set it.

The environment variables are summarized below.

Environment Variable Value Description
HF_MODEL_ID /opt/ml/model Path mounted from S3
OPTION_ROLLING_BATCH disable Disable rolling batch (async mode)
OPTION_DTYPE bf16 Model precision
OPTION_TRUST_REMOTE_CODE true Load Mamba2 custom code
OPTION_MAMBA_SSM_CACHE_DTYPE float32 Mamba2 state cache precision
OPTION_TENSOR_PARALLEL_DEGREE 1 GPU parallelism (1 A10G)
OPTION_MAX_MODEL_LEN 4096 Maximum context length
OPTION_CHAT_TEMPLATE /opt/ml/model/chat_template.jinja Chat template file

OPTION_ROLLING_BATCH=disable disables LMI's rolling batch mode, launching in async mode (default since V17). Async mode uses vLLM's AsyncLLMEngine directly, improving performance and stability over rolling batch mode. OPTION_CHAT_TEMPLATE is the path to the chat template bundled with the model, which is automatically applied to prompts during inference requests.

When creating with Python SDK

You can create the same configuration with the Python SDK instead of console operations. In a VPC closed-network configuration, specify the subnet and security group in vpc_config.

import sagemaker
import boto3

role = sagemaker.get_execution_role()
sess = sagemaker.Session()
region = "ap-northeast-1"

# LMI container URI (latest as of 2026-02: LMI V20 / DJL 0.36.0)
container_uri = sagemaker.image_uris.retrieve(
    framework="djl-lmi",
    version="0.36.0",
    region=region
)

env = {
    "HF_MODEL_ID": "/opt/ml/model",
    "OPTION_ROLLING_BATCH": "disable",
    "OPTION_DTYPE": "bf16",
    "OPTION_TRUST_REMOTE_CODE": "true",
    "OPTION_MAMBA_SSM_CACHE_DTYPE": "float32",
    "OPTION_TENSOR_PARALLEL_DEGREE": "1",
    "OPTION_MAX_MODEL_LEN": "4096",
    "OPTION_CHAT_TEMPLATE": "/opt/ml/model/chat_template.jinja",
}

# VPC information (example using default VPC)
ec2 = boto3.client("ec2", region_name=region)
vpcs = ec2.describe_vpcs(Filters=[{"Name": "isDefault", "Values": ["true"]}])
vpc_id = vpcs["Vpcs"][0]["VpcId"]
subnets = ec2.describe_subnets(Filters=[{"Name": "vpc-id", "Values": [vpc_id]}])
subnet_ids = [s["SubnetId"] for s in subnets["Subnets"]]
sgs = ec2.describe_security_groups(
    Filters=[{"Name": "vpc-id", "Values": [vpc_id]}, {"Name": "group-name", "Values": ["default"]}]
)
sg_id = sgs["SecurityGroups"][0]["GroupId"]

model = sagemaker.Model(
    image_uri=container_uri,
    model_data=f"s3://{sess.default_bucket()}/models/nemotron-9b-v2-japanese/",
    env=env,
    role=role,
    vpc_config={"Subnets": subnet_ids, "SecurityGroupIds": [sg_id]},
)

predictor = model.deploy(
    instance_type="ml.g5.2xlarge",
    initial_instance_count=1,
    container_startup_health_check_timeout=900,
    model_data_download_timeout=1800,
)

Creating an Endpoint

Once the model is created, create an endpoint configuration and deploy the endpoint.

It's recommended to change two timeout settings. Extend container_startup_health_check_timeout to 900 seconds (default: 300 seconds). Loading the 18GB model and initializing vLLM takes time, so the default may cause a timeout. Similarly, extend model_data_download_timeout to 1800 seconds for peace of mind.

Confirming Deployment Completion

When the endpoint status becomes InService, deployment is complete. In my environment, it took approximately 11 minutes (677 seconds) including model download from S3 and vLLM initialization.

As a sanity check, try sending a simple prompt.

import json
import boto3

sm_runtime = boto3.client("sagemaker-runtime", region_name="ap-northeast-1")

payload = {
    "inputs": "What is the height of Tokyo Tower?",
    "parameters": {
        "max_new_tokens": 128,
        "temperature": 0,
    }
}

response = sm_runtime.invoke_endpoint(
    EndpointName="nemotron-9b-v2-japanese",
    ContentType="application/json",
    Body=json.dumps(payload),
)
result = json.loads(response["Body"].read().decode("utf-8"))
print(result["generated_text"])

If a response is returned normally, the closed-network deployment is complete.

Pain Points Encountered

During the process from deployment to operation verification, I ran into several issues. Among them, the special token problem with the chat template was a particularly important discovery that directly impacts benchmark accuracy.

Chat Template Special Token Issue

This was the biggest discovery in this verification.

Nemotron 9B-v2-Japanese's chat template uses special tokens as role separators for System / User / Assistant. In README files and blog articles on HuggingFace Hub, you often see the notation <extra_id_0> <extra_id_1> <extra_id_2>, but these are actually not the correct special tokens.

Looking at the model's tokenizer_config.json, the actual role separator tokens are <SPECIAL_10> (ID: 10), <SPECIAL_11> (ID: 11), and <SPECIAL_12> (ID: 12). When the string <extra_id_0> is passed to the tokenizer, it's not recognized as a single special token and is split into 6 subwords: <, extra, _id, _, 0, >.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese",
    trust_remote_code=True
)

# Wrong: split into 6 subwords excluding BOS
tokenizer.encode("<extra_id_0>")
# → [1, 523, 15683, 1620, 290, 29900, 1572]  (BOS + 6 subwords)

# Correct: single special token
tokenizer.encode("<SPECIAL_10>")
# → [1, 10]  (BOS + special token)

Looking at the entire template, using the <extra_id_X> method results in 18 extra tokens being injected into the prompt just for role separators. Since these are meaningless noise to the model, they affect inference accuracy.

When actually compared in benchmarks, the <extra_id_X> method achieved an accuracy of 83.3%, while using the correct <SPECIAL_X> tokens improved it to 84.8%. A difference of 1.5pp may seem small, but in certain categories (questions with answer A), there was a significant improvement from 66.2% → 75.0%.

The reliable solution is to apply the template locally using HuggingFace's tokenizer.apply_chat_template() and send the rendered text as inputs.

from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "nvidia/NVIDIA-Nemotron-Nano-9B-v2-Japanese",
    trust_remote_code=True
)

messages = [{"role": "user", "content": "What is the height of Tokyo Tower?"}]
formatted = tokenizer.apply_chat_template(
    messages, tokenize=False,
    add_generation_prompt=True,
    enable_thinking=False,
)

# formatted correctly contains <SPECIAL_10>, <SPECIAL_11>, etc.
payload = {
    "inputs": formatted,
    "parameters": {"max_new_tokens": 128, "temperature": 0},
}

LMI Messages API Limitations

The LMI container's async mode also supports the OpenAI-compatible messages format, but it didn't work well with Nemotron 9B-v2-Japanese. All requests showed prompt_tokens: 8, and the same response was returned regardless of the prompt content.

There's a high probability that the <SPECIAL_X> tokens in the chat template are not being correctly interpreted by the LMI-side tokenizer processing. As mentioned above, applying the template locally and sending in inputs format works without issues.

S3 Model Specification Method

When using the sagemaker-core v3 SDK (sagemaker.core.resources), care is needed with the S3ModelDataSource settings. Specify "S3Prefix" for s3_data_type and explicitly set compression_type to "None". If compression_type is omitted, "Gzip" becomes the default, and an error will occur when it tries to decompress uncompressed model files.

Benchmark Comparison

JCommonsenseQA

Measured under the same conditions as the DGX Spark verification from last time.

Item Setting
Dataset JCommonsenseQA v1.1
Split validation (1,119 questions)
Evaluation Method 3-shot
temperature 0
Thinking Mode OFF
Environment vLLM Version Accuracy Correct Answers Average Latency
DGX Spark (NGC vLLM BF16) v0.13.0 83.5% 934/1,119 0.38 sec/question
SageMaker (LMI vLLM BF16) v0.15.1 84.8% 949/1,119 0.30 sec/question

The vLLM versions and GPUs differ between DGX Spark (NGC container) and SageMaker (LMI container), but the difference in accuracy is within 1.3pp. The result shows that with the same inference engine, accuracy remains nearly identical even when the hardware changes.

Cost Comparison

Item DGX Spark SageMaker (ml.g5.2xlarge)
Initial Cost $3,999 (hardware) None
Running Cost Electricity only $2.197/h
Monthly Estimate (weekdays 8h) ~ A few thousand yen ~ $352
Monthly Estimate (24h) ~ A few thousand yen ~ $1,604
Network Offline capable VPC closed-network capable
Scalability Fixed at 1 unit Auto Scaling capable

DGX Spark only requires electricity costs once purchased, making it overwhelmingly cost-effective during development and experimentation phases. On the other hand, SageMaker stops charging when you delete the endpoint when not in use, making it suitable for production environments with low usage frequency or batch processing-style usage.

Don't forget to factor in the cost of VPC endpoints (Interface type). In the Tokyo region, it's $0.014/h per ENI, which accumulates by the number of AZs × number of endpoints. Deleting VPC endpoints when not in use will save costs.

Cleanup

When verification is complete, don't forget to delete the endpoint. ml.g5.2xlarge costs $2.197/h, so leaving it running for a day results in approximately $53 in charges.

There are three items to delete. Delete them in reverse dependency order: endpoint → endpoint configuration → model.

import boto3

sm = boto3.client("sagemaker", region_name="ap-northeast-1")

# 1. Delete endpoint
sm.delete_endpoint(EndpointName="nemotron-9b-v2-japanese")

# 2. Delete endpoint configuration
sm.delete_endpoint_config(EndpointConfigName="nemotron-9b-v2-japanese")

# 3. Delete model
sm.delete_model(ModelName="nemotron-9b-v2-japanese")

If VPC endpoints are no longer needed, delete them as well. You can select the 6 endpoints created this time from the endpoint list in the VPC console and delete them.

Summary

Deploying Nemotron 9B-v2-Japanese in a closed-network configuration to the SageMaker Tokyo region itself worked without issues. Once you've configured the 6 VPC endpoints, you can achieve a configuration where data never leaves Japan's VPC. Deployment time is approximately 11 minutes, and inference latency is also practical at 0.30 seconds/question.

The biggest takeaway from this verification is that with vLLM, accuracy remains nearly identical even when the hardware changes. Despite differences in both version and GPU between DGX Spark (NGC vLLM, 83.5%) and SageMaker (LMI vLLM, 84.8%), the difference was only 1.3pp. The fact that accuracy confirmed locally can be reproduced in the cloud as long as the same inference engine is used is reassuring when selecting deployment targets.


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

Share this article