Skip to content

Commit 3992d3c

Browse files
Added fp16/bf16 based export and compile support for VLMs (quic#819)
Added fp16/bf16 based export and compile support for VLMs --------- Signed-off-by: Asmita Goswami <asmigosw@qti.qualcomm.com> Signed-off-by: Dhiraj Kumar Sah <dhirajku@qti.qualcomm.com> Signed-off-by: asmigosw <asmigosw@qti.qualcomm.com> Co-authored-by: Dhiraj Kumar Sah <dhirajku@qti.qualcomm.com>
1 parent dfaf31b commit 3992d3c

51 files changed

Lines changed: 772 additions & 198 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

QEfficient/base/modeling_qeff.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def _transform_names(self) -> List[str]:
6060
def __init__(self, model: torch.nn.Module, **kwargs) -> None:
6161
super().__init__()
6262
self.model = model
63+
self.config = model.config
6364
self.hash_params = create_model_params(self, **kwargs)
6465
self.onnx_path: Optional[str] = None
6566
self.qpc_path: Optional[str] = None
@@ -77,11 +78,51 @@ def __init__(self, model: torch.nn.Module, **kwargs) -> None:
7778
self.model, transformed = transform.apply(self.model)
7879
any_transformed = any_transformed or transformed
7980

81+
self._normalize_torch_dtype()
82+
8083
if not any_transformed:
8184
warnings.warn(f"No transforms applied to model: {self.model_name}. It may be an unsupported model!")
8285
else:
8386
logger.info(f"Pytorch transforms applied to model: {self.model_name}")
8487

88+
if self.config.torch_dtype == torch.bfloat16:
89+
logger.warning("BFloat16 dtype is not yet supported; converting to float16 precision!")
90+
91+
def _normalize_torch_dtype(self):
92+
"""
93+
Normalizes torch_dtype across all nested configs to match the top-level config.
94+
95+
This method ensures consistency by propagating the top-level torch_dtype
96+
to all nested configs (llm_config, vision_config, etc.) that may exist in
97+
multimodal models.
98+
"""
99+
top_level_dtype = getattr(self.config, "torch_dtype", torch.float32)
100+
101+
if top_level_dtype is None:
102+
top_level_dtype = torch.float32
103+
elif isinstance(top_level_dtype, str):
104+
top_level_dtype = getattr(torch, top_level_dtype, torch.float32)
105+
106+
self.config.torch_dtype = top_level_dtype
107+
108+
# Normalize llm_config if it exists
109+
if hasattr(self.config, "llm_config"):
110+
self.config.llm_config.torch_dtype = top_level_dtype
111+
if hasattr(self.config.llm_config, "use_bfloat16"):
112+
self.config.llm_config.use_bfloat16 = top_level_dtype == torch.bfloat16
113+
114+
# Normalize vision_config if it exists
115+
if hasattr(self.config, "vision_config"):
116+
self.config.vision_config.torch_dtype = top_level_dtype
117+
if hasattr(self.config.vision_config, "use_bfloat16"):
118+
self.config.vision_config.use_bfloat16 = top_level_dtype == torch.bfloat16
119+
120+
# Normalize text_config if it exists (for models like Qwen2.5-VL)
121+
if hasattr(self.config, "text_config"):
122+
self.config.text_config.torch_dtype = top_level_dtype
123+
124+
logger.info(f"Normalized all config torch_dtype to: {top_level_dtype}")
125+
85126
def _offload_model_weights(self, offload_pt_weights: bool) -> bool:
86127
"""Clear PyTorch model weights to reduce memory usage after ONNX export."""
87128
if offload_pt_weights and not self._is_weights_offloaded:
@@ -506,12 +547,21 @@ def _compile(
506547
command.append(f"-network-specialization-config={specializations_json}")
507548

508549
# Write custom_io.yaml file
550+
model_in_bfloat16 = hasattr(self, "config") and (self.config.torch_dtype == torch.bfloat16)
551+
pkv_in_bfloat16 = (custom_io is not None) and any(
552+
"past_" in key and "bfloat16" in value for key, value in custom_io.items()
553+
)
509554
if custom_io is not None:
510555
custom_io_yaml = compile_dir / "custom_io.yaml"
511556
with open(custom_io_yaml, "w") as fp:
512557
for io_name, dtype in custom_io.items():
513558
fp.write(f" - IOName: {io_name}\n Precision: {dtype}\n\n")
514-
command.append(f"-custom-IO-list-file={custom_io_yaml}")
559+
if model_in_bfloat16 and pkv_in_bfloat16:
560+
logger.warning(
561+
"Model and Past KV types are both bfloat16. Custom IO list file will be ignored during compile."
562+
)
563+
else:
564+
command.append(f"-custom-IO-list-file={custom_io_yaml}")
515565

516566
command.append(f"-aic-binary-dir={qpc_path}")
517567
logger.info(f"Running compiler: {' '.join(command)}")

QEfficient/generation/cloud_infer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def __init__(
6565

6666
# Build dtype mapping once (depends on aicapi constants)
6767
self.aic_to_np_dtype_mapping = {
68+
getattr(aicapi, "BFLOAT16_TYPE", 11): np.dtype(np.float16),
6869
aicapi.FLOAT_TYPE: np.dtype(np.float32),
6970
aicapi.FLOAT_16_TYPE: np.dtype(np.float16),
7071
aicapi.INT8_Q_TYPE: np.dtype(np.int8),

QEfficient/transformers/cache_utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -734,10 +734,16 @@ def from_legacy_cache(
734734
) -> "HybridCache":
735735
"""Converts a cache in the legacy cache format into an equivalent `DynamicCache`. Used for
736736
backward compatibility."""
737+
738+
# Get the sliding_window_pattern from config
739+
sliding_window_pattern = getattr(
740+
config, "_sliding_window_pattern", getattr(config, "sliding_window_pattern", None)
741+
)
742+
737743
cache = cls(
738744
config,
739745
batch_size=past_key_values[0][0].shape[0],
740-
max_cache_len=past_key_values[config.sliding_window_pattern - 1][0].shape[2],
746+
max_cache_len=past_key_values[sliding_window_pattern - 1][0].shape[2],
741747
sliding_window_len=past_key_values[0][0].shape[2],
742748
)
743749
if past_key_values is not None:

QEfficient/transformers/models/codegen/modeling_codegen.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def _attn(
4242
head_mask=None,
4343
):
4444
# Keep the attention weights computation in fp32 to avoid overflow issues
45-
query = query.to(torch.float32)
46-
key = key.to(torch.float32)
45+
query = query.to(value.dtype)
46+
key = key.to(value.dtype)
4747

4848
attn_weights = torch.matmul(query, key.transpose(-1, -2))
4949

@@ -349,8 +349,7 @@ def forward(
349349
# Cast to INT32 to avoid issue while running in ONNXRT
350350
logit_index = position_ids.to(torch.int32).argmax(1, keepdim=True)
351351
hidden_states = transformer_outputs[0][torch.arange(position_ids.shape[0]).view(-1, 1), logit_index]
352-
lm_logits = self.lm_head(hidden_states)
353-
352+
lm_logits = self.lm_head(hidden_states).float()
354353
return CausalLMOutputWithPast(
355354
loss=None,
356355
logits=lm_logits,

QEfficient/transformers/models/falcon/modeling_falcon.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,11 @@ def forward(
142142
attention_scores = query_layer @ key_layer.transpose(-1, -2)
143143
attention_scores /= math.sqrt(self.head_dim)
144144
attention_scores = torch.where(
145-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attention_scores
145+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=self.config.torch_dtype), attention_scores
146+
)
147+
attention_scores = F.softmax(attention_scores + attention_mask, dim=-1, dtype=torch.float32).to(
148+
query_layer.dtype
146149
)
147-
attention_scores = F.softmax(attention_scores + attention_mask, dim=-1, dtype=hidden_states.dtype)
148150
# It is unclear why neither dropout nor head_mask is applied here (while it is with alibi).
149151
attn_output = attention_scores @ value_layer
150152

@@ -401,7 +403,7 @@ def forward(
401403
# Cast to INT32 to avoid issue while running in ONNXRT
402404
logit_index = position_ids.to(torch.int32).argmax(1, keepdim=True)
403405
hidden_states = transformer_outputs[0][torch.arange(position_ids.shape[0]).view(-1, 1), logit_index]
404-
lm_logits = self.lm_head(hidden_states)
406+
lm_logits = self.lm_head(hidden_states).float()
405407

406408
return CausalLMOutputWithCrossAttentions(
407409
loss=None,

QEfficient/transformers/models/gemma/modeling_gemma.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def eager_attention_forward(
101101
attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
102102
if attention_mask is not None:
103103
attn_weights = torch.where(
104-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
104+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=module.config.torch_dtype), attn_weights
105105
)
106106

107107
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)

QEfficient/transformers/models/gemma2/modeling_gemma2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ def eager_attention_forward(
108108
attn_weights = torch.matmul(query, key_states.transpose(2, 3)) * scaling
109109
if attention_mask is not None:
110110
attn_weights = torch.where(
111-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
111+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=module.config.torch_dtype), attn_weights
112112
)
113113

114114
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
@@ -448,7 +448,7 @@ def forward(
448448
logits = logits / self.config.final_logit_softcapping
449449
logits = torch.tanh(logits)
450450
logits = logits * self.config.final_logit_softcapping
451-
451+
logits = logits.float()
452452
return CausalLMOutputWithPast(
453453
loss=None,
454454
logits=logits,

QEfficient/transformers/models/gemma3/modeling_gemma3.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@
3838
class GemmaRMSNormFunc(torch.autograd.Function):
3939
@staticmethod
4040
def forward(hidden_states: torch.Tensor, weight: torch.Tensor, epsilon: float):
41-
hidden_states = hidden_states.to(torch.float32)
42-
div_first = hidden_states * torch.rsqrt(torch.tensor(hidden_states.shape[-1], dtype=torch.float32))
41+
div_first = hidden_states * torch.rsqrt(torch.tensor(hidden_states.shape[-1], dtype=hidden_states.dtype))
4342
variance = div_first.pow(2).sum(-1, keepdim=True)
4443
hidden_states = hidden_states * torch.rsqrt(variance + epsilon)
4544
return weight * hidden_states
@@ -61,7 +60,7 @@ class QEffGemma3CustomRMSNormAIC(nn.Module):
6160
def forward(self, hidden_states):
6261
return GemmaRMSNormFunc.apply(
6362
hidden_states,
64-
self.weight.float() + 1.0,
63+
(self.weight).to(hidden_states.dtype) + 1.0,
6564
self.variance_epsilon if hasattr(self, "variance_epsilon") else self.eps,
6665
)
6766

@@ -164,7 +163,7 @@ def eager_attention_forward(
164163

165164
if attention_mask is not None:
166165
attn_weights = torch.where(
167-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
166+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=module.config.torch_dtype), attn_weights
168167
)
169168

170169
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
@@ -198,7 +197,7 @@ def __qeff_init__(self):
198197
config = copy.deepcopy(self.config)
199198
config.rope_theta = config.rope_local_base_freq
200199
config.rope_scaling = {"rope_type": "default", "factor": 1.0}
201-
self.is_local = _is_local(self.layer_idx, self.config.sliding_window_pattern)
200+
self.is_local = _is_local(self.layer_idx, self.config._sliding_window_pattern)
202201
self.window = self.config.sliding_window if self.is_local else None
203202

204203
self.rotary_emb_local = QEffGemma3RotaryEmbedding(
@@ -253,7 +252,7 @@ def forward(
253252
"batch_index": batch_index,
254253
"position_ids": position_ids,
255254
"is_sliding": self.is_sliding,
256-
"sliding_window_pattern": self.config.sliding_window_pattern,
255+
"sliding_window_pattern": self.config._sliding_window_pattern,
257256
"sliding_window": past_key_values.sliding_window_len,
258257
}
259258
if comp_ctx_lengths is not None:
@@ -272,7 +271,9 @@ def forward(
272271

273272
if attention_mask is not None: # no matter the length, we just slice it
274273
attn_weights = torch.where(
275-
attention_mask.bool(), torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
274+
attention_mask.bool(),
275+
torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=self.config.torch_dtype),
276+
attn_weights,
276277
)
277278

278279
# upcast attention to fp32
@@ -322,7 +323,7 @@ def forward(
322323
else:
323324
attention_mask = _create_causal_mask(
324325
position_ids=position_ids,
325-
target_length=past_key_value.key_cache[self.config.sliding_window_pattern - 1].shape[-2],
326+
target_length=past_key_value.key_cache[self.config._sliding_window_pattern - 1].shape[-2],
326327
)
327328

328329
hidden_states, self_attn_weights = self.self_attn(
@@ -534,6 +535,9 @@ def forward(
534535
)
535536
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
536537

538+
if self.config.torch_dtype == torch.float16:
539+
logger.warning("Accuracy might drop with float16 as torch_dtype")
540+
537541
outputs = self.model(
538542
input_ids=input_ids,
539543
attention_mask=attention_mask,
@@ -551,7 +555,7 @@ def forward(
551555
)
552556
logit_index = position_ids.to(torch.int32).argmax(1, keepdim=True)
553557
hidden_states = outputs[0][torch.arange(position_ids.shape[0]).view(-1, 1), logit_index]
554-
logits = self.lm_head(hidden_states)
558+
logits = self.lm_head(hidden_states).float()
555559

556560
if self.config.final_logit_softcapping is not None:
557561
logits = logits / self.config.final_logit_softcapping
@@ -569,7 +573,9 @@ def forward(
569573
def get_dummy_pkv_cache(self, config, batch_size, seq_len):
570574
n_heads = config.num_key_value_heads
571575
d_head = config.head_dim
572-
layer_switch = config.sliding_window_pattern if hasattr(config, "sliding_window_pattern") else 2 # 2 is for BC
576+
layer_switch = (
577+
config._sliding_window_pattern if hasattr(config, "_sliding_window_pattern") else 2
578+
) # 2 is for BC
573579
is_sliding = torch.tensor(
574580
[bool((i + 1) % layer_switch) for i in range(config.num_hidden_layers)], dtype=torch.bool
575581
)
@@ -581,8 +587,8 @@ def get_dummy_pkv_cache(self, config, batch_size, seq_len):
581587
for i in range(config.num_hidden_layers):
582588
if hasattr(config, "sliding_window"):
583589
cache_shape = global_cache_shape if not is_sliding[i] else sliding_cache_shape
584-
new_layer_key_cache = torch.zeros(cache_shape, dtype=torch.float32)
585-
new_layer_value_cache = torch.zeros(cache_shape, dtype=torch.float32)
590+
new_layer_key_cache = torch.zeros(cache_shape, dtype=self.config.torch_dtype)
591+
new_layer_value_cache = torch.zeros(cache_shape, dtype=self.config.torch_dtype)
586592
pkv = (new_layer_key_cache, new_layer_value_cache)
587593
past_key_values.append(pkv)
588594
return past_key_values
@@ -835,15 +841,15 @@ def get_onnx_dynamic_axes(
835841
pkv_dynamic_axes = {0: "full_batch_size" if continuous_batching else "batch_size", 2: "ctx_len"}
836842
pkv_dynamic_sliding_axes = {0: "full_batch_size" if continuous_batching else "batch_size", 2: "sliding_window"}
837843
layer_switch = (
838-
self.language_model.config.sliding_window_pattern
839-
if hasattr(self.language_model.config, "sliding_window_pattern")
844+
self.language_model.config._sliding_window_pattern
845+
if hasattr(self.language_model.config, "_sliding_window_pattern")
840846
else 2
841847
)
842848
for i in range(self.language_model.config.num_hidden_layers):
843849
for kv in ["key", "value"]:
844850
apply_dynamic_axes = (
845851
pkv_dynamic_sliding_axes
846-
if ((i + 1) % layer_switch and hasattr(self.language_model.config, "sliding_window_pattern"))
852+
if ((i + 1) % layer_switch and hasattr(self.language_model.config, "_sliding_window_pattern"))
847853
else pkv_dynamic_axes
848854
)
849855
lang_dynamic_axes[f"past_{kv}.{i}"] = apply_dynamic_axes
@@ -881,7 +887,9 @@ def get_output_names(self, kv_offload: bool = False):
881887
def get_dummy_pkv_cache(self, config, batch_size, seq_len):
882888
n_heads = config.num_key_value_heads
883889
d_head = config.head_dim
884-
layer_switch = config.sliding_window_pattern if hasattr(config, "sliding_window_pattern") else 2 # 2 is for BC
890+
layer_switch = (
891+
config._sliding_window_pattern if hasattr(config, "_sliding_window_pattern") else 2
892+
) # 2 is for BC
885893
is_sliding = torch.tensor(
886894
[bool((i + 1) % layer_switch) for i in range(config.num_hidden_layers)], dtype=torch.bool
887895
)
@@ -893,8 +901,8 @@ def get_dummy_pkv_cache(self, config, batch_size, seq_len):
893901
for i in range(config.num_hidden_layers):
894902
if hasattr(config, "sliding_window"):
895903
cache_shape = global_cache_shape if not is_sliding[i] else sliding_cache_shape
896-
new_layer_key_cache = torch.zeros(cache_shape, dtype=torch.float32)
897-
new_layer_value_cache = torch.zeros(cache_shape, dtype=torch.float32)
904+
new_layer_key_cache = torch.zeros(cache_shape, dtype=self.config.torch_dtype)
905+
new_layer_value_cache = torch.zeros(cache_shape, dtype=self.config.torch_dtype)
898906
pkv = (new_layer_key_cache, new_layer_value_cache)
899907
past_key_values.append(pkv)
900908
return past_key_values
@@ -931,9 +939,9 @@ def get_dummy_inputs(
931939
# Define inputs
932940
vision_inputs = {}
933941
lang_inputs = {}
934-
vision_inputs["pixel_values"] = torch.zeros((inputs_shapes["pixel_values"]), dtype=torch.float32)
942+
vision_inputs["pixel_values"] = torch.zeros((inputs_shapes["pixel_values"]), dtype=self.config.torch_dtype)
935943
lang_inputs["input_ids"] = torch.zeros((inputs_shapes["input_ids"]), dtype=torch.int64)
936-
lang_inputs["vision_embeds"] = torch.zeros((inputs_shapes["vision_embeds"]), dtype=torch.float32)
944+
lang_inputs["vision_embeds"] = torch.zeros((inputs_shapes["vision_embeds"]), dtype=self.config.torch_dtype)
937945
lang_inputs["position_ids"] = (
938946
torch.arange(constants.ONNX_EXPORT_EXAMPLE_SEQ_LEN, dtype=torch.int64)
939947
.view(1, constants.ONNX_EXPORT_EXAMPLE_SEQ_LEN)
@@ -972,7 +980,7 @@ def get_inputs_info(self):
972980
IOInfo(name="attention_mask", datatype=torch.int64, shape=("batch_size", "seq_len")),
973981
IOInfo(
974982
name="pixel_values",
975-
datatype=torch.float32,
983+
datatype=self.config.torch_dtype,
976984
shape=("batch_size", 3, "img_size", "img_size"),
977985
),
978986
]

QEfficient/transformers/models/gpt2/modeling_gpt2.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ def eager_attention_forward(module, query, key, value, attention_mask, head_mask
4040
if attention_mask is not None:
4141
# Apply the attention mask
4242
attn_weights = torch.where(
43-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
43+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=module.config.torch_dtype), attn_weights
4444
)
4545

46-
attn_weights = nn.functional.softmax(attn_weights, dim=-1)
46+
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32)
4747

4848
# Downcast (if necessary) back to V's dtype (if in mixed-precision) -- No-Op otherwise
4949
attn_weights = attn_weights.type(value.dtype)

QEfficient/transformers/models/gpt_bigcode/modeling_gpt_bigcode.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def eager_attention_forward(
8484

8585
if attention_mask is not None:
8686
attn_weights = torch.where(
87-
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=torch.float32), attn_weights
87+
attention_mask, torch.tensor(MIN_MASKED_ATTENTION_VALUE, dtype=module.config.torch_dtype), attn_weights
8888
)
8989
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
9090
attn_output = torch.matmul(attn_weights, value_states)
@@ -439,7 +439,7 @@ def forward(
439439
# Cast to INT32 to avoid issue while running in ONNXRT
440440
logit_index = position_ids.to(torch.int32).argmax(1, keepdim=True)
441441
hidden_states = transformer_outputs[0][torch.arange(position_ids.shape[0]).view(-1, 1), logit_index]
442-
lm_logits = self.lm_head(hidden_states)
442+
lm_logits = self.lm_head(hidden_states).float()
443443

444444
return CausalLMOutputWithCrossAttentions(
445445
loss=None,

0 commit comments

Comments
 (0)